;;; io.ms
;;; Copyright 1984-2017 Cisco Systems, Inc.
;;; 
;;; Licensed under the Apache License, Version 2.0 (the "License");
;;; you may not use this file except in compliance with the License.
;;; You may obtain a copy of the License at
;;; 
;;; http://www.apache.org/licenses/LICENSE-2.0
;;; 
;;; Unless required by applicable law or agreed to in writing, software
;;; distributed under the License is distributed on an "AS IS" BASIS,
;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
;;; See the License for the specific language governing permissions and
;;; limitations under the License.

(define (native-string->bytevector s)
  (string->bytevector s (native-transcoder)))

; convert uses of custom-port-warning? to warning? if custom-port warnings
; are enabled in io.ss
(define (custom-port-warning? x) #t)

(define prettytest.ss (format "~a/prettytest.ss" *mats-dir*))

(mat port-operations
  (error? (close-port cons))
  ; the following several clauses test various open-file-output-port options
  (let ([p (open-file-output-port "testfile.ss" (file-options no-fail))])
    (and (port? p) (output-port? p) (begin (close-port p) #t)))
  (error? ; file already exists
    (open-file-output-port "testfile.ss"))
  (error? ; file already exists
    (open-file-output-port "testfile.ss" (file-options compressed)))
  (let ([p (open-file-output-port "testfile.ss" (file-options replace))])
    (and (port? p) (output-port? p) (begin (close-port p) #t)))
  (let ([p (open-file-output-port "testfile.ss" (file-options no-fail))])
    (and (port? p) (output-port? p) (begin (close-port p) #t)))
  (let ([p (open-file-output-port "testfile.ss" (file-options no-fail))])
    (put-bytevector p (native-string->bytevector "\"hello"))
    (close-port p)
    (let ([p (open-file-output-port "testfile.ss" (file-options no-fail no-truncate append))])
      (put-bytevector p (native-string->bytevector " there\""))
      (close-port p)
      (let ([p (open-file-input-port "testfile.ss")])
        (and (equal? (get-bytevector-all p) (native-string->bytevector "\"hello there\""))
             (eof-object? (get-u8 p))
             (begin (close-port p)
                    #t)))))
  (let ([p (let loop () (if (file-exists? "testfile.ss")
                            (begin (delete-file "testfile.ss" #f) (loop))
                            (open-file-output-port "testfile.ss")))])
    (for-each (lambda (x)
                (put-bytevector p (native-string->bytevector x))
                (put-bytevector p (native-string->bytevector " ")))
              '("a" "b" "c" "d" "e"))
    (put-bytevector p (native-string->bytevector "\n"))
    (close-port p)
    #t)
  (equal? (let ([p (open-file-input-port "testfile.ss")])
            (let f ([x (get-u8 p)])
              (if (eof-object? x)
                  (begin (close-port p) '())
                  (cons (integer->char x) (f (get-u8 p))))))
          (if (eq? (native-eol-style) 'crlf)
              '(#\a #\space #\b #\space #\c #\space
                #\d #\space #\e #\space #\return #\newline)
              '(#\a #\space #\b #\space #\c #\space
                #\d #\space #\e #\space #\newline)))
  (error? (call-with-port 3 values))
  (error? (call-with-port (current-input-port) 'a))
  (equal? (call-with-values
              (lambda ()
                (call-with-port
                 (open-file-output-port "testfile.ss" (file-options replace))
                 (lambda (p)
                   (for-each (lambda (c) (put-u8 p (char->integer c)))
                             (string->list "a b c d e"))
                   (values 1 2 3))))
            list)
          '(1 2 3))
  (equal? (call-with-port
           (open-file-input-port "testfile.ss")
           (lambda (p)
             (list->string
              (let f ()
                (let ([c (get-u8 p)])
                  (if (eof-object? c)
                      '()
                      (begin (unget-u8 p c)
                             (let ([c (get-u8 p)])
                               (cons (integer->char c) (f))))))))))
          "a b c d e")
  (equal? (call-with-port
           (open-file-input-port "testfile.ss")
           (lambda (p)
             (list->string
              (let f ()
                (let ([c (get-u8 p)])
                  (unget-u8 p c)
                  (if (eof-object? c)
                      (begin
                        (unless (and (eof-object? (lookahead-u8 p))
                                     (port-eof? p)
                                     (eof-object? (get-u8 p)))
                          (errorf #f "unget of eof apparently failed"))
                        '())
                      (let ([c (get-u8 p)])
                        (cons (integer->char c) (f)))))))))
          "a b c d e")
  (andmap (lambda (p)
            (equal? (call-with-port
                     p
                     (lambda (p)
                       (list->string
                        (let f ()
                          (let ([c (lookahead-u8 p)])
                            (if (eof-object? c)
                                '()
                                (let ([c (get-u8 p)])
                                  (cons (integer->char c) (f)))))))))
                    "a b c d e"))
          (list (open-file-input-port "testfile.ss")
                (open-bytevector-input-port '#vu8(97 32 98 32 99 32 100 32 101))
                (open-bytevector-input-port (bytevector->immutable-bytevector '#vu8(97 32 98 32 99 32 100 32 101)))))
  ; test various errors related to input ports
  (begin (set! ip (open-file-input-port "testfile.ss"))
         (and (port? ip) (input-port? ip)))
  (error? ; unget can only follow get
    (unget-u8 ip 40))
  (eqv? (get-u8 ip) (char->integer #\a))
  (begin (unget-u8 ip (char->integer #\a)) (eqv? (get-u8 ip) (char->integer #\a)))
  (error? (put-u8 ip (char->integer #\a)))
  (error? (put-bytevector ip #vu8()))
  (error? (flush-output-port ip))
  (begin (close-port ip) #t)
  (begin (close-port ip) #t)
  (error? (port-eof? ip))
  (error? (input-port-ready? ip))
  (error? (get-u8? ip))
  (error? (lookahead-u8? ip))
  (error? (unget-u8? ip))
  (error? (get-bytevector-n ip 1))
  (error? (get-bytevector-n! ip (make-bytevector 10) 0 10))
  (error? (get-bytevector-some ip))
  (error? (get-bytevector-all ip))
  ; test various errors related to output ports
  (begin (set! op (open-file-output-port "testfile.ss" (file-options replace)))
         (and (port? op) (output-port? op)))
  (error? (input-port-ready? op))
  (error? (lookahead-u8 op))
  (error? (get-u8 op))
  (error? (unget-u8 op 40))
  (error? (get-bytevector-n op 1))
  (error? (get-bytevector-n! op (make-bytevector 10) 0 10))
  (error? (get-bytevector-some op))
  (error? (get-bytevector-all op))
  (begin (close-port op) #t)
  (begin (close-port op) #t)
  (error? (put-u8 op (char->integer #\a)))
  (error? (put-bytevector op #vu8(1)))
  (error? (flush-output-port op))

  (let ([s (native-string->bytevector "hi there, mom!")])
    (let ([ip (open-bytevector-input-port s)])
      (let-values ([(op op-ex) (open-bytevector-output-port)])
        (do ([c (get-u8 ip) (get-u8 ip)])
            ((eof-object? c)
             (equal? (op-ex) s))
             (unget-u8 ip c)
             (put-u8 op (get-u8 ip))))))

  (error? (eof-object #!eof))
  (eq? (call-with-port (open-bytevector-input-port #vu8()) get-u8) #!eof)
  (eq? (call-with-port (open-bytevector-input-port #vu8()) get-u8) (eof-object))
  (eq? (eof-object) #!eof)
  (let ([s (native-string->bytevector "hi there, mom!")])
    (equal?
      (call-with-port (open-bytevector-input-port s)
        (lambda (i)
          (call-with-bytevector-output-port
            (lambda (o)
              (do ([c (get-u8 i) (get-u8 i)])
                  ((eof-object? c))
                (unget-u8 i c)
                (put-u8 o (get-u8 i)))))))
      s))

  ; the following makes sure that call-with-port closes the at least on
  ; systems which restrict the number of open ports to less than 2048
  (let ([filename "testfile.ss"])
    (let loop ((i 2048))
      (or (zero? i)
          (begin
            (call-with-port
             (open-file-output-port filename (file-options replace))
             (lambda (p) (put-u8 p (quotient i 256)) (put-u8 p (modulo i 256))))
            (and (eq? (call-with-port
                       (open-file-input-port filename)
                       (lambda (p)
                         (let* ([hi (get-u8 p)]
                                [lo (get-u8 p)])
                           (+ (* 256 hi) lo))))
                      i)
                 (loop (- i 1)))))))
  (begin
    (close-input-port #%$console-input-port)
    (not (port-closed? #%$console-input-port)))
  (begin
    (close-output-port #%$console-output-port)
    (not (port-closed? #%$console-output-port)))
 )

(mat port-operations1
  (error? ; incorrect number of arguments
    (open-file-input-port))
  (error? ; furball is not a string
    (open-file-input-port 'furball))
  (error? ; not a file-options object
    (open-file-input-port "testfile.ss" '()))
  (error? ; not a valid buffer mode
    (open-file-input-port "testfile.ss" (file-options) 17))
  (error? ; not a transcoder
    (open-file-input-port "testfile.ss" (file-options) (buffer-mode block) 'wow))
  (error? ; incorrect number of arguments
    (open-file-input-port "testfile.ss" (file-options) (buffer-mode block) #f 'now?))
  (error? ; cannot open
    (open-file-input-port "/probably/not/a/good/path"))
  (error? ; cannot open
    (open-file-input-port "/probably/not/a/good/path" (file-options compressed)))
  (error? ; invalid options
    (open-file-input-port "testfile.ss" (file-options uncompressed)))
  (error? ; invalid options
    (open-file-input-port "testfile.ss" (file-options truncate)))
  (error? ; incorrect number of arguments
    (open-file-output-port))
  (error? ; furball is not a string
    (open-file-output-port 'furball))
  (error? ; not a file-options object
    (open-file-output-port "testfile.ss" '(no-create)))
  (error? ; not a valid buffer mode
    (open-file-output-port "testfile.ss" (file-options) 17))
  (error? ; not a transcoder
    (open-file-output-port "testfile.ss" (file-options) (buffer-mode block) 'wow))
  (error? ; incorrect number of arguments
    (open-file-output-port "testfile.ss" (file-options) (buffer-mode block) #f 'now?))
  (error? ; cannot open
    (open-file-output-port "/probably/not/a/good/path"))
  (error? ; invalid options
    (open-file-output-port "testfile.ss" (file-options uncompressed)))
  (error? ; invalid options
    (open-file-output-port "testfile.ss" (file-options truncate)))
  (error? ; incorrect number of arguments
    (open-file-input/output-port))
  (error? ; furball is not a string
    (open-file-input/output-port 'furball))
  (error? ; not a file-options object
    (open-file-input/output-port "testfile.ss" '(no-create)))
  (error? ; not a valid buffer mode
    (open-file-input/output-port "testfile.ss" (file-options) 17))
  (error? ; not a transcoder
    (open-file-input/output-port "testfile.ss" (file-options) (buffer-mode block) 'wow))
  (error? ; incorrect number of arguments
    (open-file-input/output-port "testfile.ss" (file-options) (buffer-mode block) #f 'now?))
  (error? ; cannot open
    (open-file-input/output-port "/probably/not/a/good/path"))
  (error? ; invalid options
    (open-file-input/output-port "testfile.ss" (file-options uncompressed)))
  (error? ; invalid options
    (open-file-input/output-port "testfile.ss" (file-options truncate)))
  (begin (delete-file "testfile.ss") #t)
  (error? ; no such file
    (open-file-input-port "testfile.ss"))
  (error? ; no such file
    (open-file-output-port "testfile.ss" (file-options no-create)))
  (error? ; no such file
    (open-file-input/output-port "testfile.ss" (file-options no-create)))
  (begin (mkdir "testfile.ss") #t)
  (guard (c [(and (i/o-filename-error? c)
                  (equal? (i/o-error-filename c) "testfile.ss"))])
    (open-file-output-port "testfile.ss" (file-options no-create)))
  (guard (c [(and (i/o-filename-error? c)
                  (equal? (i/o-error-filename c) "testfile.ss"))])
    (open-file-input/output-port "testfile.ss" (file-options no-create)))
  (begin (delete-directory "testfile.ss") #t)
  (begin
    (define $ppp (open-file-input/output-port "testfile.ss" (file-options replace)))
    (and (input-port? $ppp) (output-port? $ppp) (port? $ppp)))
  (error? (set-port-length! $ppp -3))
  (error? (set-port-length! $ppp 'all-the-way))
  (eof-object?
   (begin
     (set-port-length! $ppp 0)
     (set-port-position! $ppp 0)
     (put-bytevector $ppp (native-string->bytevector "hello"))
     (flush-output-port $ppp)
     (get-u8 $ppp)))
  (equal? (begin (set-port-position! $ppp 0) (get-bytevector-all $ppp))
          (native-string->bytevector "hello"))
  (eqv? (begin
          (put-bytevector $ppp (native-string->bytevector "goodbye\n"))
          (truncate-port $ppp 9)
          (port-position $ppp))
        9)
  (eof-object? (get-u8 $ppp))
  (eqv? (begin (set-port-position! $ppp 0) (port-position $ppp)) 0)
  (equal? (get-bytevector-all $ppp) (native-string->bytevector "hellogood"))
  (eqv? (begin
          (put-bytevector $ppp (native-string->bytevector "byebye\n"))
          (truncate-port $ppp 0)
          (port-position $ppp))
        0)
  (eof-object? (get-u8 $ppp))
  (eof-object?
   (begin
     (close-port $ppp)
     (let ([ip (open-file-input-port "testfile.ss")])
       (let ([c (get-u8 ip)])
         (close-port $ppp)
         (close-port ip)
         c))))
  (error?
   (let ([ip (open-file-input-port "testfile.ss")])
     (dynamic-wind
         void
         (lambda () (truncate-port ip))
         (lambda () (close-port ip)))))
  (error? (truncate-port 'animal-crackers))
  (error? (truncate-port))
  (error? (truncate-port $ppp))
  (let-values ([(op get) (open-bytevector-output-port)])
    (and (= (port-position op) 0)
         (= (port-length op) 0)
         (do ([i 4000 (fx- i 1)])
             ((fx= i 0) #t)
           (put-bytevector op (string->utf8 "hello")))
         (= (port-length op) 20000)
         (= (port-position op) 20000)
         (begin (set-port-position! op 5000) #t)
         (= (port-position op) 5000)
         (= (port-length op) 20000)
         (begin (truncate-port op) #t)
         (= (port-position op) 0)
         (= (port-length op) 0)
         (begin (truncate-port op 17) #t)
         (= (port-position op) 17)
         (= (port-length op) 17)
         (begin (put-bytevector op (string->utf8 "okay")) #t)
         (= (port-position op) 21)
         (= (port-length op) 21)
         (let ([bv (get)])
           (and (= (char->integer #\o) (bytevector-u8-ref bv 17))
                (= (char->integer #\k) (bytevector-u8-ref bv 18))
                (= (char->integer #\a) (bytevector-u8-ref bv 19))
                (= (char->integer #\y) (bytevector-u8-ref bv 20))))
         (= (port-position op) 0)
         (= (port-length op) 0)
         (begin (put-u8 op (char->integer #\a))
                (put-u8 op (char->integer #\newline))
                #t)
         (= (port-position op) 2)
         (equal? (get) (string->utf8 "a\n"))))
  (let ([ip (open-bytevector-input-port (native-string->bytevector "beam me up, scotty!"))]
        [bv (make-bytevector 10)])
    (and (= (port-position ip) 0)
         (= (port-length ip) 19)
         (not (eof-object? (lookahead-u8 ip)))
         (equal? (get-bytevector-n ip 4) (native-string->bytevector "beam"))
         (= (port-position ip) 4)
         (not (eof-object? (lookahead-u8 ip)))
         (equal? (get-bytevector-n! ip bv 0 10) 10)
         (equal? bv (native-string->bytevector " me up, sc"))
         (= (port-position ip) 14)
         (equal? (get-bytevector-n! ip bv 0 10) 5)
         (equal? bv (native-string->bytevector "otty!p, sc"))
         (= (port-position ip) 19)
         (eof-object? (lookahead-u8 ip))
         (eof-object? (get-u8 ip))
         (eof-object? (get-bytevector-n! ip bv 0 10))
         (= (get-bytevector-n! ip bv 0 0) 0) ;; TODO: check w/ Kent about this
         (begin
           (set-port-position! ip 10)
           (= (port-position ip) 10))
         (equal? (get-bytevector-n! ip bv 0 10) 9)
         (equal? bv (native-string->bytevector ", scotty!c"))))
)

(mat port-operations2
  (equal?
    (let* ([op (open-file-output-port "testfile.ss" (file-options replace) (buffer-mode none))]
           [ip (open-file-input-port "testfile.ss")])
      (put-u8 op 97)
      (let* ([b1 (get-u8 ip)] [b2 (get-u8 ip)])
        (put-u8 op 98)
        (let* ([b3 (get-u8 ip)] [b4 (get-u8 ip)])
          (put-u8 op 99)
          (let ([b5 (get-u8 ip)])
            (close-port op)
              (let ([b6 (get-u8 ip)])
                (close-port ip)
                (list b1 b2 b3 b4 b5 b6))))))
    '(97 #!eof 98 #!eof 99 #!eof))
  (equal?
    (let* ([op (open-file-output-port "testfile.ss" (file-options replace) (buffer-mode none))]
           [ip (open-file-input-port "testfile.ss")])
      (let ([eof1? (port-eof? ip)])
        (put-u8 op 97)
       ; the port-eof? call above buffers the eof, so b1 should be #!eof
        (let* ([b1 (get-u8 ip)] [b2 (get-u8 ip)])
          (put-u8 op 98)
          (let* ([eof2? (port-eof? ip)] [b3 (get-u8 ip)])
            (let ([b4 (get-u8 ip)])
              (put-u8 op 99)
              (let* ([b5 (get-u8 ip)])
                (close-port op)
                (let* ([b6 (get-u8 ip)] [eof3? (port-eof? ip)])
                  (close-port ip)
                  (list eof1? b1 b2 eof2? b3 b4 b5 b6 eof3?))))))))
    '(#t #!eof 97 #f 98 #!eof 99 #!eof #t))
  (equal?
   ; following assumes block buffering really doesn't cause any writes until
   ; at least after a few bytes have been written
    (let* ([op (open-file-output-port "testfile.ss" (file-options replace) (buffer-mode block))]
           [ip (open-file-input-port "testfile.ss")])
      (put-u8 op 97)
      (let ([b1 (get-u8 ip)])
        (put-u8 op 98)
        (let ([b2 (get-u8 ip)])
          (close-port op)
          (let* ([b3 (get-u8 ip)] [b4 (get-u8 ip)] [b5 (get-u8 ip)])
            (close-port ip)
            (list b1 b2 b3 b4 b5)))))
    '(#!eof #!eof 97 98 #!eof))
 ; test switching between input and output modes
 ; should be adapted for textual ports
  (equal?
    (begin
      (call-with-port
        (open-file-output-port "testfile.ss" (file-options replace))
        (lambda (p) (put-bytevector p #vu8(1 2 3 4 5))))
      (let ([iop (open-file-input/output-port "testfile.ss"
                   (file-options no-fail no-truncate))])
        (let ([b1 (get-u8 iop)])
          (put-u8 iop 17)
          (let ([b2 (get-u8 iop)])
            (close-port iop)
            (list b1 b2
              (call-with-port
                (open-file-input-port "testfile.ss")
                get-bytevector-all))))))
    '(1 3 #vu8(1 17 3 4 5)))
 ; test switching between input and output modes
 ; old implementation is broken---uncomment for new implementation
 ; and move to set of mats testing convenience i/o
  #;(equal?
    (begin
      (with-output-to-file "testfile.ss"
        (lambda () (display "hi there"))
        'replace)
      (let ([iop (open-input-output-file "testfile.ss")])
        (let ([c1 (read-char iop)])
          (write-char #\! iop)
          (let ([c2 (read-char iop)])
            (close-port iop)
            (list c1 c2
              (with-input-from-file "testfile.ss"
                (lambda ()
                  (list->string
                    (let f ()
                      (let ([c (read-char)])
                        (if (eof-object? c)
                            '()
                            (cons c (f)))))))))))))
    '(#\h #\space "h! there"))
  (equal?
    (let-values ([(p g) (open-string-output-port)])
      (fresh-line p)
      (fresh-line p)
      (display "hello" p)
      (fresh-line p)
      (fresh-line p)
      (newline p)
      (fresh-line p)
      (display "goodbye" p)
      (newline p)
      (fresh-line p)
      (g))
    "hello\n\ngoodbye\n")
 ; check for bug fix in transcoded-port-put-some
  (let f ([n 1000])
    (or (fx= n 0)
        (begin
          (let ([op (open-file-output-port "testfile.ss" (file-options replace)
                      (buffer-mode line) (native-transcoder))])
            (do ([i 1000 (- i 1)])
                ((fx= i 0))
              (display #!eof op))
            (close-port op))
          (and (equal? (call-with-port
                         (open-file-input-port "testfile.ss" (file-options)
                           (buffer-mode block) (native-transcoder))
                         get-string-all)
                       (apply string-append (make-list 1000 "#!eof")))
               (f (- n 1))))))
)

(mat port-operations3
  (error? (file-port? "not a port"))
  (error? (port-file-descriptor 'oops))
  (error? (port-file-descriptor (open-input-string "hello")))
  (or (threaded?) (file-port? (console-input-port)))
  (or (threaded?) (file-port? (console-output-port)))
  (not (file-port? (open-input-string "hello")))
  (or (threaded?) (= (port-file-descriptor (console-input-port)) 0))
  (or (threaded?) (= (port-file-descriptor (console-output-port)) 1))
  (> (let ([ip (open-input-file prettytest.ss)])
       (let ([n (and (file-port? ip) (port-file-descriptor ip))])
         (close-port ip)
         n))
     1)
  (> (let ([ip (open-input-file prettytest.ss 'compressed)])
       (let ([n (and (file-port? ip) (port-file-descriptor ip))])
         (close-port ip)
         n))
     1)
  (> (let ([op (open-output-file "testfile.ss" '(replace))])
       (let ([n (and (file-port? op) (port-file-descriptor op))])
         (close-port op)
         n))
     1)
  (> (let ([op (open-output-file "testfile.ss" '(replace compressed))])
       (let ([n (and (file-port? op) (port-file-descriptor op))])
         (close-port op)
         n))
     1)
 )

(if (case (machine-type)
     [(pb) #t]
     [else (embedded?)])
    (mat iconv-codec
      (error? (errorf 'iconv-codec "-73 is not a string"))
      (error? (errorf 'transcoded-port "unsupported encoding almost certainly bogus"))
      (error? (errorf 'close-port "iconv CP1252 codec cannot encode #\\x3BB"))
      (error? (errorf 'close-port "iconv CP1252 codec cannot encode #\\newline with eol-style ls"))
      (error? (errorf 'close-port "latin-1 codec cannot encode #\\newline with eol-style ls")))
    (mat iconv-codec
      (error? ; invalid codec
        (iconv-codec -73))
      (error? ; unsupported encoding
        (let ()
          (define codec (iconv-codec "almost certainly bogus"))
          (define transcoder
            (make-transcoder codec
              (eol-style none)
              (error-handling-mode ignore)))
          (define-values (bp get) (open-bytevector-output-port))
          (define op (transcoded-port bp transcoder))
          (newline op)
          (close-port op)))
      (let ()
        (define codec (iconv-codec "UTF-8"))
        (define transcoder
          (make-transcoder codec
            (eol-style none)
            (error-handling-mode ignore)))
        (define op
          (open-file-output-port "testfile.ss"
            (file-options replace)
            (buffer-mode line)
            transcoder))
        (define p1)
        (define p2)
        (define p3)
        (define p4)
        (newline op)
        (display "hello l\x0;ambda:\n\x3bb;!\n" op)
        (close-port op)
        (and
          (equal?
            (call-with-port (open-file-input-port "testfile.ss" (file-options)
                              (buffer-mode block)
                              (make-transcoder (utf-8-codec) (eol-style none)
                                (error-handling-mode raise)))
              (lambda (ip)
                (set! p1 (port-position ip))
                (let ([s (get-string-all ip)])
                  (set! p2 (port-position ip))
                  s)))
            "\nhello l\x0;ambda:\n\x3bb;!\n")
          (equal?
            (call-with-port (open-file-input-port "testfile.ss" (file-options)
                              (buffer-mode block)
                              transcoder)
              (lambda (ip)
                (set! p3 (port-position ip))
                (let ([s (get-string-all ip)])
                  (set! p4 (port-position ip))
                  s)))
            "\nhello l\x0;ambda:\n\x3bb;!\n")
          (eq? p1 0)
          (eq? p2 20)
          (eq? p3 0)
          (eq? p4 20)))
      (let () ; same but eol-style lf
        (define codec (iconv-codec "UTF-8"))
        (define transcoder
          (make-transcoder codec
            (eol-style lf)
            (error-handling-mode ignore)))
        (define op
          (open-file-output-port "testfile.ss"
            (file-options replace)
            (buffer-mode line)
            transcoder))
        (define p1)
        (define p2)
        (define p3)
        (define p4)
        (newline op)
        (display "hello l\x0;ambda:\n\x3bb;!\n" op)
        (close-port op)
        (and
          (equal?
            (call-with-port (open-file-input-port "testfile.ss" (file-options)
                              (buffer-mode block)
                              (make-transcoder (utf-8-codec) (eol-style lf)
                                (error-handling-mode raise)))
              (lambda (ip)
                (set! p1 (port-position ip))
                (let ([s (get-string-all ip)])
                  (set! p2 (port-position ip))
                  s)))
            "\nhello l\x0;ambda:\n\x3bb;!\n")
          (equal?
            (call-with-port (open-file-input-port "testfile.ss" (file-options)
                              (buffer-mode block)
                              transcoder)
              (lambda (ip)
                (set! p3 (port-position ip))
                (let ([s (get-string-all ip)])
                  (set! p4 (port-position ip))
                  s)))
            "\nhello l\x0;ambda:\n\x3bb;!\n")
          (eq? p1 0)
          (eq? p2 20)
          (eq? p3 0)
          (eq? p4 20)))
      (let () ; same but eol-style crlf
        (define codec (iconv-codec "UTF-8"))
        (define transcoder
          (make-transcoder codec
            (eol-style crlf)
            (error-handling-mode ignore)))
        (define op
          (open-file-output-port "testfile.ss"
            (file-options replace)
            (buffer-mode line)
            transcoder))
        (define p1)
        (define p2)
        (define p3)
        (define p4)
        (newline op)
        (display "hello l\x0;ambda:\n\x3bb;!\n" op)
        (close-port op)
        (and
          (equal?
            (call-with-port (open-file-input-port "testfile.ss" (file-options)
                              (buffer-mode block)
                              (make-transcoder (utf-8-codec) (eol-style crlf)
                                (error-handling-mode raise)))
              (lambda (ip)
                (set! p1 (port-position ip))
                (let ([s (get-string-all ip)])
                  (set! p2 (port-position ip))
                  s)))
            "\nhello l\x0;ambda:\n\x3bb;!\n")
          (equal?
            (call-with-port (open-file-input-port "testfile.ss" (file-options)
                              (buffer-mode block)
                              transcoder)
              (lambda (ip)
                (set! p3 (port-position ip))
                (let ([s (get-string-all ip)])
                  (set! p4 (port-position ip))
                  s)))
            "\nhello l\x0;ambda:\n\x3bb;!\n")
          (eq? p1 0)
          (eq? p2 23)
          (eq? p3 0)
          (eq? p4 23)))
      (let ()
        (define codec (iconv-codec "GB18030"))
        (define transcoder
          (make-transcoder codec
            (eol-style none)
            (error-handling-mode raise)))
        (define op
          (open-file-output-port "testfile.ss"
            (file-options replace)
            (buffer-mode line)
            transcoder))
        (newline op)
        (display "hello l\x0;ambda:\n\x3bb;!\n" op)
        (close-port op)
        (and
          (equal?
            (call-with-port (open-file-input-port "testfile.ss")
              get-bytevector-all)
            #vu8(#x0a #x68 #x65 #x6c #x6c #x6f #x20 #x6c #x00 #x61 #x6d #x62 #x64 #x61 #x3a #x0a #xa6 #xcb #x21 #x0a))
          (equal?
            (call-with-port (open-file-input-port "testfile.ss" (file-options)
                              (buffer-mode block)
                              transcoder)
              get-string-all)
            "\nhello l\x0;ambda:\n\x3bb;!\n")))
      (let ()
        (define codec (iconv-codec "CP1252"))
        (define transcoder
          (make-transcoder codec
            (eol-style none)
            (error-handling-mode replace)))
        (define op
          (open-file-output-port "testfile.ss"
            (file-options replace)
            (buffer-mode line)
            transcoder))
        (newline op)
        (display "hello l\x0;ambda:\n\x3bb;!\n" op)
        (close-port op)
        (and
          (equal?
            (call-with-port (open-file-input-port "testfile.ss")
              get-bytevector-all)
            #vu8(#x0a #x68 #x65 #x6c #x6c #x6f #x20 #x6c #x00 #x61 #x6d #x62 #x64 #x61 #x3a #x0a #x3f #x21 #x0a))
          (equal?
            (call-with-port (open-file-input-port "testfile.ss" (file-options)
                              (buffer-mode block)
                              transcoder)
              get-string-all)
            "\nhello l\x0;ambda:\n?!\n")))
      (let () ; same but eol-style lf
        (define codec (iconv-codec "CP1252"))
        (define transcoder
          (make-transcoder codec
            (eol-style lf)
            (error-handling-mode replace)))
        (define op
          (open-file-output-port "testfile.ss"
            (file-options replace)
            (buffer-mode line)
            transcoder))
        (newline op)
        (display "hello l\x0;ambda:\n\x3bb;!\n" op)
        (close-port op)
        (and
          (equal?
            (call-with-port (open-file-input-port "testfile.ss")
              get-bytevector-all)
            #vu8(#x0a #x68 #x65 #x6c #x6c #x6f #x20 #x6c #x00 #x61 #x6d #x62 #x64 #x61 #x3a #x0a #x3f #x21 #x0a))
          (equal?
            (call-with-port (open-file-input-port "testfile.ss" (file-options)
                              (buffer-mode block)
                              transcoder)
              get-string-all)
            "\nhello l\x0;ambda:\n?!\n")))
      (let () ; same but eol-style crlf
        (define codec (iconv-codec "CP1252"))
        (define transcoder
          (make-transcoder codec
            (eol-style crlf)
            (error-handling-mode replace)))
        (define op
          (open-file-output-port "testfile.ss"
            (file-options replace)
            (buffer-mode line)
            transcoder))
        (newline op)
        (display "hello l\x0;ambda:\n\x3bb;!\n" op)
        (close-port op)
        (and
          (equal?
            (call-with-port (open-file-input-port "testfile.ss")
              get-bytevector-all)
            #vu8(#x0d #x0a #x68 #x65 #x6c #x6c #x6f #x20 #x6c #x00 #x61 #x6d #x62 #x64 #x61 #x3a #x0d #x0a #x3f #x21 #x0d #x0a))
          (equal?
            (call-with-port (open-file-input-port "testfile.ss" (file-options)
                              (buffer-mode block)
                              transcoder)
              get-string-all)
            "\nhello l\x0;ambda:\n?!\n")))
      (let ()
        (define codec (iconv-codec "CP1252"))
        (define transcoder
          (make-transcoder codec
            (eol-style none)
            (error-handling-mode ignore)))
        (define op
          (open-file-output-port "testfile.ss"
            (file-options replace)
            (buffer-mode line)
            transcoder))
        (newline op)
        (display "hello l\x0;ambda:\n\x3bb;!\n" op)
        (close-port op)
        (and
          (equal?
            (call-with-port (open-file-input-port "testfile.ss")
              get-bytevector-all)
            #vu8(#x0a #x68 #x65 #x6c #x6c #x6f #x20 #x6c #x00 #x61 #x6d #x62 #x64 #x61 #x3a #x0a #x21 #x0a))
          (equal?
            (call-with-port (open-file-input-port "testfile.ss" (file-options)
                              (buffer-mode block)
                              transcoder)
              get-string-all)
            "\nhello l\x0;ambda:\n!\n")))
      (error? ; encoding error
        (let-values ([(bp get) (open-bytevector-output-port)])
          (define codec (iconv-codec "CP1252"))
          (define transcoder
            (make-transcoder codec
              (eol-style none)
              (error-handling-mode raise)))
          (define op (transcoded-port bp transcoder))
          (newline op)
          (display "hello l\x0;ambda: \x3bb;!\n" op)
          (close-port op)))
      (error? ; encoding error
        (let-values ([(bp get) (open-bytevector-output-port)])
          (define codec (iconv-codec "CP1252"))
          (define transcoder
            (make-transcoder codec
              (eol-style ls)
              (error-handling-mode raise)))
          (define op (transcoded-port bp transcoder))
          (newline op)
          (close-port op)))
      ; some (older?) versions of iconv don't handle unassigned code-page 1252
      ; characters properly.  c'est la vie.
      #;(let ()
          (define bp (open-bytevector-input-port #vu8(#x80 #x81 #x82 #x8c #x8d #x8e #x8f #x90 #x91 #x9c #x9d #x9e)))
          (define codec (iconv-codec "CP1252"))
          (define transcoder
            (make-transcoder codec
              (eol-style none)
              (error-handling-mode replace)))
          (define ip (transcoded-port bp transcoder))
          (equal?
            (get-string-all ip)
            "\x20ac;\xfffd;\x201a;\x0152;\xfffd;\x017d;\xfffd;\xfffd;\x2018;\x0153;\xfffd;\x017e;"))
      #;(let ()
          (define bp (open-bytevector-input-port #vu8(#x80 #x81 #x82 #x8c #x8d #x8e #x8f #x90 #x91 #x9c #x9d #x9e)))
          (define codec (iconv-codec "CP1252"))
          (define transcoder
            (make-transcoder codec
              (eol-style none)
              (error-handling-mode ignore)))
          (define ip (transcoded-port bp transcoder))
          (equal?
            (get-string-all ip)
            "\x20ac;\x201a;\x0152;\x017d;\x2018;\x0153;\x017e;"))
      #;(error? ; decoding error
          (let ()
            (define bp (open-bytevector-input-port #vu8(#x80 #x81 #x82 #x8c #x8d #x8e #x8f #x90 #x91 #x9c #x9d #x9e)))
            (define codec (iconv-codec "CP1252"))
            (define transcoder
              (make-transcoder codec
                (eol-style none)
                (error-handling-mode raise)))
            (define ip (transcoded-port bp transcoder))
            (equal?
              (get-string-all ip)
              "\x20ac;\xfffd;\x201a;\x0152;\xfffd;\x017d;\xfffd;\xfffd;\x2018;\x0153;\xfffd;\x017e;")))
      (let () ; SBCS CP1252
        (define cp1252
          '((#x00 #x0000) (#x01 #x0001) (#x02 #x0002) (#x03 #x0003)
                          (#x04 #x0004) (#x05 #x0005) (#x06 #x0006) (#x07 #x0007)
                          (#x08 #x0008) (#x09 #x0009) (#x0A #x000A) (#x0B #x000B)
                          (#x0C #x000C) (#x0D #x000D) (#x0E #x000E) (#x0F #x000F)
                          (#x10 #x0010) (#x11 #x0011) (#x12 #x0012) (#x13 #x0013)
                          (#x14 #x0014) (#x15 #x0015) (#x16 #x0016) (#x17 #x0017)
                          (#x18 #x0018) (#x19 #x0019) (#x1A #x001A) (#x1B #x001B)
                          (#x1C #x001C) (#x1D #x001D) (#x1E #x001E) (#x1F #x001F)
                          (#x20 #x0020) (#x21 #x0021) (#x22 #x0022) (#x23 #x0023)
                          (#x24 #x0024) (#x25 #x0025) (#x26 #x0026) (#x27 #x0027)
                          (#x28 #x0028) (#x29 #x0029) (#x2A #x002A) (#x2B #x002B)
                          (#x2C #x002C) (#x2D #x002D) (#x2E #x002E) (#x2F #x002F)
                          (#x30 #x0030) (#x31 #x0031) (#x32 #x0032) (#x33 #x0033)
                          (#x34 #x0034) (#x35 #x0035) (#x36 #x0036) (#x37 #x0037)
                          (#x38 #x0038) (#x39 #x0039) (#x3A #x003A) (#x3B #x003B)
                          (#x3C #x003C) (#x3D #x003D) (#x3E #x003E) (#x3F #x003F)
                          (#x40 #x0040) (#x41 #x0041) (#x42 #x0042) (#x43 #x0043)
                          (#x44 #x0044) (#x45 #x0045) (#x46 #x0046) (#x47 #x0047)
                          (#x48 #x0048) (#x49 #x0049) (#x4A #x004A) (#x4B #x004B)
                          (#x4C #x004C) (#x4D #x004D) (#x4E #x004E) (#x4F #x004F)
                          (#x50 #x0050) (#x51 #x0051) (#x52 #x0052) (#x53 #x0053)
                          (#x54 #x0054) (#x55 #x0055) (#x56 #x0056) (#x57 #x0057)
                          (#x58 #x0058) (#x59 #x0059) (#x5A #x005A) (#x5B #x005B)
                          (#x5C #x005C) (#x5D #x005D) (#x5E #x005E) (#x5F #x005F)
                          (#x60 #x0060) (#x61 #x0061) (#x62 #x0062) (#x63 #x0063)
                          (#x64 #x0064) (#x65 #x0065) (#x66 #x0066) (#x67 #x0067)
                          (#x68 #x0068) (#x69 #x0069) (#x6A #x006A) (#x6B #x006B)
                          (#x6C #x006C) (#x6D #x006D) (#x6E #x006E) (#x6F #x006F)
                          (#x70 #x0070) (#x71 #x0071) (#x72 #x0072) (#x73 #x0073)
                          (#x74 #x0074) (#x75 #x0075) (#x76 #x0076) (#x77 #x0077)
                          (#x78 #x0078) (#x79 #x0079) (#x7A #x007A) (#x7B #x007B)
                          (#x7C #x007C) (#x7D #x007D) (#x7E #x007E) (#x7F #x007F)
                          (#x80 #x20AC) (#x82 #x201A) (#x83 #x0192) (#x84 #x201E)
                          (#x85 #x2026) (#x86 #x2020) (#x87 #x2021) (#x88 #x02C6)
                          (#x89 #x2030) (#x8A #x0160) (#x8B #x2039) (#x8C #x0152)
                          (#x8E #x017D) (#x91 #x2018) (#x92 #x2019) (#x93 #x201C)
                          (#x94 #x201D) (#x95 #x2022) (#x96 #x2013) (#x97 #x2014)
                          (#x98 #x02DC) (#x99 #x2122) (#x9A #x0161) (#x9B #x203A)
                          (#x9C #x0153) (#x9E #x017E) (#x9F #x0178) (#xA0 #x00A0)
                          (#xA1 #x00A1) (#xA2 #x00A2) (#xA3 #x00A3) (#xA4 #x00A4)
                          (#xA5 #x00A5) (#xA6 #x00A6) (#xA7 #x00A7) (#xA8 #x00A8)
                          (#xA9 #x00A9) (#xAA #x00AA) (#xAB #x00AB) (#xAC #x00AC)
                          (#xAD #x00AD) (#xAE #x00AE) (#xAF #x00AF) (#xB0 #x00B0)
                          (#xB1 #x00B1) (#xB2 #x00B2) (#xB3 #x00B3) (#xB4 #x00B4)
                          (#xB5 #x00B5) (#xB6 #x00B6) (#xB7 #x00B7) (#xB8 #x00B8)
                          (#xB9 #x00B9) (#xBA #x00BA) (#xBB #x00BB) (#xBC #x00BC)
                          (#xBD #x00BD) (#xBE #x00BE) (#xBF #x00BF) (#xC0 #x00C0)
                          (#xC1 #x00C1) (#xC2 #x00C2) (#xC3 #x00C3) (#xC4 #x00C4)
                          (#xC5 #x00C5) (#xC6 #x00C6) (#xC7 #x00C7) (#xC8 #x00C8)
                          (#xC9 #x00C9) (#xCA #x00CA) (#xCB #x00CB) (#xCC #x00CC)
                          (#xCD #x00CD) (#xCE #x00CE) (#xCF #x00CF) (#xD0 #x00D0)
                          (#xD1 #x00D1) (#xD2 #x00D2) (#xD3 #x00D3) (#xD4 #x00D4)
                          (#xD5 #x00D5) (#xD6 #x00D6) (#xD7 #x00D7) (#xD8 #x00D8)
                          (#xD9 #x00D9) (#xDA #x00DA) (#xDB #x00DB) (#xDC #x00DC)
                          (#xDD #x00DD) (#xDE #x00DE) (#xDF #x00DF) (#xE0 #x00E0)
                          (#xE1 #x00E1) (#xE2 #x00E2) (#xE3 #x00E3) (#xE4 #x00E4)
                          (#xE5 #x00E5) (#xE6 #x00E6) (#xE7 #x00E7) (#xE8 #x00E8)
                          (#xE9 #x00E9) (#xEA #x00EA) (#xEB #x00EB) (#xEC #x00EC)
                          (#xED #x00ED) (#xEE #x00EE) (#xEF #x00EF) (#xF0 #x00F0)
                          (#xF1 #x00F1) (#xF2 #x00F2) (#xF3 #x00F3) (#xF4 #x00F4)
                          (#xF5 #x00F5) (#xF6 #x00F6) (#xF7 #x00F7) (#xF8 #x00F8)
                          (#xF9 #x00F9) (#xFA #x00FA) (#xFB #x00FB) (#xFC #x00FC)
                          (#xFD #x00FD) (#xFE #x00FE) (#xFF #x00FF)))
        (define transcoder
          (make-transcoder (iconv-codec "CP1252")
            (eol-style none)
            (error-handling-mode raise)))
        (define ls
          (append cp1252
            (let ([v (list->vector cp1252)])
              (let f ([n 100000])
                (if (fx= n 0)
                    '()
                    (cons
                      (vector-ref v (random (vector-length v)))
                      (f (fx- n 1))))))))
        (define s (apply string (map integer->char (map cadr ls))))
        (define op
          (open-file-output-port "testfile.ss"
            (file-options replace) (buffer-mode block)
            transcoder))
        #;(put-string op s)
        (let loop ([i 0] [n (string-length s)])
          (unless (fx= n 0)
            (let ([k (fx+ (random n) 1)])
              (put-string op s i k)
              (loop (fx+ i k) (fx- n k)))))
        (close-port op)
        (and
          (equal?
            (call-with-port (open-file-input-port "testfile.ss")
              get-bytevector-all)
            (apply bytevector (map car ls)))
          (equal?
            (call-with-port (open-file-input-port "testfile.ss"
                              (file-options) (buffer-mode block)
                              transcoder)
              #;get-string-all
              (lambda (ip)
                (let ([t (make-string (string-length s))])
                  (let loop ([i 0] [n (string-length s)])
                    (unless (fx= n 0)
                      (let ([k (fx+ (random n) 1)])
                        (get-string-n! ip t i k)
                        (loop (fx+ i k) (fx- n k)))))
                  t)))
            s)))
      (let () ; MBCS UTF-8
        (define transcoder
          (make-transcoder (iconv-codec "UTF-8")
            (eol-style none)
            (error-handling-mode raise)))
        (define ls1
          (let f ([i 0])
            (if (fx= i #x11000)
                '()
                (if (fx= i #xD800)
                    (f #xE000)
                    (cons i (f (fx+ i 1)))))))
        (define ls2
          (let f ([n 1000000])
            (if (fx= n 0)
                '()
                (cons
                  (let ([n (random (- #x110000 (- #xE000 #xD800)))])
                    (if (<= #xD800 n #xDFFF)
                        (+ n (- #xE000 #xD800))
                        n))
                  (f (fx- n 1))))))
        (define s (apply string (map integer->char (append ls1 ls2))))
        #;(define s (apply string (map integer->char ls1)))
        #;(define s "hello\x1447A;")
        (define op
          (open-file-output-port "testfile.ss"
            (file-options replace) (buffer-mode block)
            transcoder))
        #;(put-string op s)
        (let loop ([i 0] [n (string-length s)])
          (unless (fx= n 0)
            (let ([k (fx+ (random n) 1)])
              (put-string op s i k)
              (loop (fx+ i k) (fx- n k)))))
        (close-port op)
        (and
          (equal?
            (call-with-port (open-file-input-port "testfile.ss"
                              (file-options) (buffer-mode block)
                              (make-transcoder (utf-8-codec) (eol-style none)
                                (error-handling-mode raise)))
              get-string-all)
            s)
          (equal?
            (call-with-port (open-file-input-port "testfile.ss"
                              (file-options) (buffer-mode block)
                              transcoder)
              #;get-string-all
              (lambda (ip)
                (let ([t (make-string (string-length s))])
                  (let loop ([i 0] [n (string-length s)])
                    (unless (fx= n 0)
                      (let ([k (fx+ (random n) 1)])
                        (get-string-n! ip t i k)
                        (loop (fx+ i k) (fx- n k)))))
                  t)))
            s)))
      (error? ; encoding error
        (let ()
          (define transcoder
            (make-transcoder (latin-1-codec)
              (eol-style ls)
              (error-handling-mode raise)))
          (define-values (bp get) (open-bytevector-output-port))
          (define op (transcoded-port bp transcoder))
          (newline op)
          (close-port op)))
      ; NB: keep this last among the iconv-codec mats
      ; close any files left open by failing iconv tests.  this is particulary
      ; important on windows when the iconv dll isn't available and where keeping
      ; file open can prevent it from being reopened.
      (begin (collect (collect-maximum-generation)) #t)
      ))

(mat port-operations4
  (begin
    (define po4-tx (make-transcoder (latin-1-codec) (eol-style none) (error-handling-mode raise)))
    #t)
  (transcoder? po4-tx)
  (not (transcoder? (latin-1-codec)))
  (eq? (call-with-port 
         (open-file-output-port "testfile.ss" (file-options replace)
           (buffer-mode block) po4-tx)
         (lambda (op) (put-string op "hi there")))
       (void))
 ; binary input port
  (begin
    (define po4-p (open-file-input-port "testfile.ss"))
    #t)
  (and (input-port? po4-p) (binary-port? po4-p) (file-port? po4-p))
  (error? (put-string po4-p "hello"))
  (error? (put-bytevector po4-p #vu8(100)))
  (error? (get-string-all po4-p))
  (error? (get-char po4-p))
  (error? (lookahead-char po4-p))
  (fixnum? (port-file-descriptor po4-p))
  (port-has-port-position? po4-p)
  (eqv? (port-position po4-p) 0)
  (port-has-set-port-position!? po4-p)
  (eq? (set-port-position! po4-p 3) (void))
  (eqv? (port-position po4-p) 3)
  (equal? (get-bytevector-n po4-p 5) (string->bytevector "there" po4-tx))
  (eof-object? (get-bytevector-n po4-p 1))
  (port-has-port-length? po4-p)
  (eqv? (port-length po4-p) 8)
  (not (port-has-set-port-length!? po4-p))
  (error? (set-port-length! po4-p 7))
  (eq? (close-port po4-p) (void))
 ; textual input port
  (begin
    (define po4-p
      (open-file-input-port "testfile.ss" (file-options)
        (buffer-mode block) po4-tx))
    #t)
  (and (input-port? po4-p) (textual-port? po4-p) (file-port? po4-p))
  (error? (put-string po4-p "hello"))
  (error? (put-bytevector po4-p #vu8(100)))
  (error? (get-bytevector-all po4-p))
  (error? (get-u8 po4-p))
  (error? (lookahead-u8 po4-p))
  (fixnum? (port-file-descriptor po4-p))
  (port-has-port-position? po4-p)
  (eqv? (port-position po4-p) 0)
  (port-has-set-port-position!? po4-p)
  (eqv? (set-port-position! po4-p 3) (void))
  (eqv? (port-position po4-p) 3)
  (equal? (get-string-n po4-p 5) "there")
  (eof-object? (get-string-n po4-p 1))
  (port-has-port-length? po4-p)
  (eqv? (port-length po4-p) 8)
  (not (port-has-set-port-length!? po4-p))
  (error? (set-port-length! po4-p 7))
  (eq? (close-port po4-p) (void))
 ; binary output port
  (begin
    (define po4-p
      (open-file-output-port "testfile.ss" (file-options replace)))
    #t)
  (and (output-port? po4-p) (binary-port? po4-p) (file-port? po4-p))
  (error? (get-string-all po4-p))
  (error? (get-char po4-p))
  (error? (lookahead-char po4-p))
  (error? (get-bytevector-all po4-p))
  (error? (get-u8 po4-p))
  (error? (lookahead-u8 po4-p))
  (error? (put-string po4-p "hello"))
  (fixnum? (port-file-descriptor po4-p))
  (port-has-port-position? po4-p)
  (eqv? (port-position po4-p) 0)
  (port-has-set-port-position!? po4-p)
  (eq? (set-port-position! po4-p 3) (void))
  (eqv? (port-position po4-p) 3)
  (eq? (put-bytevector po4-p (string->bytevector "123456" po4-tx)) (void))
  (port-has-port-length? po4-p)
  (eqv? (port-length po4-p) 9)
  (port-has-set-port-length!? po4-p)
  (eq? (set-port-length! po4-p 7) (void))
  (eq? (set-port-position! po4-p 0) (void))
  (eq? (put-bytevector po4-p (string->bytevector "abcd" po4-tx)) (void))
  (eq? (close-port po4-p) (void))
  (equal?
    (call-with-port
      (open-file-input-port "testfile.ss" (file-options)
        (buffer-mode block) po4-tx)
      get-string-all)
    "abcd234")
 ; textual output port
  (begin
    (define po4-p
      (open-file-output-port "testfile.ss" (file-options replace)
        (buffer-mode block) po4-tx))
    #t)
  (and (output-port? po4-p) (textual-port? po4-p) (file-port? po4-p))
  (error? (get-string-all po4-p))
  (error? (get-char po4-p))
  (error? (lookahead-char po4-p))
  (error? (get-bytevector-all po4-p))
  (error? (get-u8 po4-p))
  (error? (lookahead-u8 po4-p))
  (error? (put-bytevector po4-p #vu8()))
  (fixnum? (port-file-descriptor po4-p))
  (port-has-port-position? po4-p)
  (eqv? (port-position po4-p) 0)
  (port-has-set-port-position!? po4-p)
  (eq? (set-port-position! po4-p 3) (void))
  (eqv? (port-position po4-p) 3)
  (eq? (put-string po4-p "abcdef") (void))
  (port-has-port-length? po4-p)
  (eqv? (port-length po4-p) 9)
  (port-has-set-port-length!? po4-p)
  (eq? (set-port-length! po4-p 7) (void))
  (eq? (set-port-position! po4-p 0) (void))
  (eq? (put-string po4-p "1234") (void))
  (eq? (close-port po4-p) (void))
  (equal?
    (call-with-port
      (open-file-input-port "testfile.ss" (file-options)
        (buffer-mode block) po4-tx)
      get-string-all)
    "1234bcd")
 ; binary input/output port
  (begin
    (define po4-p
      (open-file-input/output-port "testfile.ss" (file-options replace)))
    #t)
  (and (output-port? po4-p) (binary-port? po4-p) (file-port? po4-p))
  (and (input-port? po4-p) (binary-port? po4-p) (file-port? po4-p))
  (fixnum? (port-file-descriptor po4-p))
  (port-has-port-position? po4-p)
  (eqv? (port-position po4-p) 0)
  (port-has-set-port-position!? po4-p)
  (eq? (set-port-position! po4-p 3) (void))
  (eqv? (port-position po4-p) 3)
  (eq? (put-bytevector po4-p (string->bytevector "foobar" po4-tx)) (void))
  (port-has-port-length? po4-p)
  (eqv? (port-length po4-p) 9)
  (port-has-set-port-length!? po4-p)
  (eq? (set-port-length! po4-p 7) (void))
  (eq? (set-port-position! po4-p 0) (void))
  (eq? (put-bytevector po4-p (string->bytevector "4321" po4-tx)) (void))
  (equal? (get-bytevector-all po4-p) (string->bytevector "oob" po4-tx))
  (eq? (set-port-position! po4-p 0) (void))
  (equal? (get-bytevector-all po4-p) (string->bytevector "4321oob" po4-tx))
  (eq? (close-port po4-p) (void))
  (equal?
    (call-with-port
      (open-file-input-port "testfile.ss" (file-options)
        (buffer-mode block) po4-tx)
      get-string-all)
    "4321oob")
 ; textual input/output port
  (begin
    (define po4-p
      (open-file-input/output-port "testfile.ss" (file-options replace)
        (buffer-mode block) po4-tx))
    #t)
  (and (output-port? po4-p) (textual-port? po4-p) (file-port? po4-p))
  (and (input-port? po4-p) (textual-port? po4-p) (file-port? po4-p))
  (fixnum? (port-file-descriptor po4-p))
  (port-has-port-position? po4-p)
  (eqv? (port-position po4-p) 0)
  (port-has-set-port-position!? po4-p)
  (eq? (set-port-position! po4-p 3) (void))
  (eqv? (port-position po4-p) 3)
  (eq? (put-string po4-p "abcdef") (void))
  (port-has-port-length? po4-p)
  (eqv? (port-length po4-p) 9)
  (port-has-set-port-length!? po4-p)
  (eq? (set-port-length! po4-p 7) (void))
  (eq? (set-port-position! po4-p 0) (void))
  (eq? (put-string po4-p "1234") (void))
  (equal? (get-string-all po4-p) "bcd")
  (eq? (set-port-position! po4-p 0) (void))
  (equal? (get-string-all po4-p) "1234bcd")
  (eq? (close-port po4-p) (void))
  (equal?
    (call-with-port
      (open-file-input-port "testfile.ss" (file-options)
        (buffer-mode block) po4-tx)
      get-string-all)
    "1234bcd")
)

(mat get-line
  (error? ; not a port
    (get-line "current-input-port"))
  (error? ; not a port
    (get-line 3))
  (error? ; not a textual input port
    (get-line (open-bytevector-input-port #vu8(1 2 3 4 5))))
  (begin
    (with-output-to-file "testfile.ss"
      (lambda ()
        (display "hello from line 1!\n")
        (display (make-string 1017 #\a))
        (display " hello from line 2!\n")
        (display "goodbye from (incomplete) line 3!"))
      'replace)
    (define $tip (open-input-file "testfile.ss"))
    #t)
  (equal? (get-line $tip) "hello from line 1!")
  (equal? (get-line $tip) (format "~a hello from line 2!" (make-string 1017 #\a)))
  (equal? (get-line $tip) "goodbye from (incomplete) line 3!")
  (eof-object? (get-line $tip))
  (eqv? (close-port $tip) (void))
  (begin
    (with-output-to-file "testfile.ss"
      (lambda ()
        (display "hello from line 1!\n")
        (display "\n")
        (display "goodbye from (complete) line 3!\n"))
      'replace)
    (define $tip (open-input-file "testfile.ss"))
    #t)
  (equal? (get-line $tip) "hello from line 1!")
  (equal? (get-line $tip) "")
  (equal? (get-line $tip) "goodbye from (complete) line 3!")
  (eof-object? (get-line $tip))
  (eqv? (close-port $tip) (void))
)

(mat low-level-port-operations
  (<= (textual-port-input-index (console-input-port))
      (textual-port-input-size (console-input-port))
      (string-length (textual-port-input-buffer (console-input-port))))
  (<= (textual-port-input-count (console-input-port))
      (string-length (textual-port-input-buffer (console-input-port))))
  (<= (textual-port-output-index (console-output-port))
      (textual-port-output-size (console-output-port))
      (string-length (textual-port-output-buffer (console-output-port))))
  (<= (textual-port-output-count (console-output-port))
      (string-length (textual-port-output-buffer (console-output-port))))
  (begin
    (define $tip (open-string-input-port "hello"))
    (define $top (let-values ([(op get) (open-string-output-port)]) (set-textual-port-output-buffer! op "hello") op))
    (define $bip (open-bytevector-input-port #vu8(1 2 3 4 5)))
    (define $bop (let-values ([(op get) (open-bytevector-output-port)]) (set-binary-port-output-buffer! op #vu8(1 2 3 4 5)) op))
    #t)
  ; textual input
  (andmap (lambda (str)
            (equal?
             (let ([ip (open-string-input-port str)])
               (let ([buffer0 (textual-port-input-buffer ip)]
                     [index0 (textual-port-input-index ip)]
                     [size0 (textual-port-input-size ip)]
                     [count0 (textual-port-input-count ip)])
                 (read-char ip)
                 (list
                  (list buffer0 index0 size0 count0)
                  (list
                   (textual-port-input-buffer ip)
                   (textual-port-input-index ip)
                   (textual-port-input-size ip)
                   (textual-port-input-count ip)))))
             '(("hello" 0 5 5) ("hello" 1 5 4))))
          (list "hello"
                (string->immutable-string "hello")))
  (equal?
    (let ([ip (open-string-input-port "hello")])
      (let ([buffer0 (textual-port-input-buffer ip)]
            [index0 (textual-port-input-index ip)]
            [size0 (textual-port-input-size ip)]
            [count0 (textual-port-input-count ip)])
        (read-char ip)
        (set-textual-port-input-buffer! ip "goodbye")
        (read-char ip)
        (list
          (list buffer0 index0 size0 count0)
          (list
            (textual-port-input-buffer ip)
            (textual-port-input-index ip)
            (textual-port-input-size ip)
            (textual-port-input-count ip)))))
    '(("hello" 0 5 5) ("goodbye" 1 7 6)))
  (equal?
    (let ([ip (open-string-input-port "hello")])
      (let ([buffer0 (textual-port-input-buffer ip)]
            [index0 (textual-port-input-index ip)]
            [size0 (textual-port-input-size ip)]
            [count0 (textual-port-input-count ip)])
        (read-char ip)
        (set-textual-port-input-size! ip 4)
        (read-char ip)
        (list
          (list buffer0 index0 size0 count0)
          (list
            (textual-port-input-buffer ip)
            (textual-port-input-index ip)
            (textual-port-input-size ip)
            (textual-port-input-count ip)))))
    '(("hello" 0 5 5) ("hello" 1 4 3)))
  (equal?
    (let ([ip (open-string-input-port "hello")])
      (let ([buffer0 (textual-port-input-buffer ip)]
            [index0 (textual-port-input-index ip)]
            [size0 (textual-port-input-size ip)]
            [count0 (textual-port-input-count ip)])
        (read-char ip)
        (set-textual-port-input-index! ip 4)
        (read-char ip)
        (list
          (list buffer0 index0 size0 count0)
          (list
            (textual-port-input-buffer ip)
            (textual-port-input-index ip)
            (textual-port-input-size ip)
            (textual-port-input-count ip)))))
    '(("hello" 0 5 5) ("hello" 5 5 0)))
  (error? ; not a textual input port
    (textual-port-input-buffer $top))
  (error? ; not a textual input port
    (textual-port-input-buffer $bip))
  (error? ; not a textual input port
    (textual-port-input-buffer $bop))
  (error? ; not a textual input port
    (textual-port-input-buffer 75))
  (error? ; not a textual input port
    (textual-port-input-index $top))
  (error? ; not a textual input port
    (textual-port-input-index $bip))
  (error? ; not a textual input port
    (textual-port-input-index $bop))
  (error? ; not a textual input port
    (textual-port-input-index 75))
  (error? ; not a textual input port
    (textual-port-input-size $top))
  (error? ; not a textual input port
    (textual-port-input-size $bip))
  (error? ; not a textual input port
    (textual-port-input-size $bop))
  (error? ; not a textual input port
    (textual-port-input-size 75))
  (error? ; not a textual input port
    (textual-port-input-count $top))
  (error? ; not a textual input port
    (textual-port-input-count $bip))
  (error? ; not a textual input port
    (textual-port-input-count $bop))
  (error? ; not a textual input port
    (textual-port-input-count 75))
  (error? ; not a textual input port
    (set-textual-port-input-buffer! $top ""))
  (error? ; not a textual input port
    (set-textual-port-input-buffer! $bip ""))
  (error? ; not a textual input port
    (set-textual-port-input-buffer! $bop ""))
  (error? ; not a textual input port
    (set-textual-port-input-buffer! 75 ""))
  (error? ; not a textual input port
    (set-textual-port-input-index! $top 0))
  (error? ; not a textual input port
    (set-textual-port-input-index! $bip 0))
  (error? ; not a textual input port
    (set-textual-port-input-index! $bop 0))
  (error? ; not a textual input port
    (set-textual-port-input-index! 75 0))
  (error? ; not a textual input port
    (set-textual-port-input-size! $top 0))
  (error? ; not a textual input port
    (set-textual-port-input-size! $bip 0))
  (error? ; not a textual input port
    (set-textual-port-input-size! $bop 0))
  (error? ; not a textual input port
    (set-textual-port-input-size! 75 0))
  (error? ; not a string
    (set-textual-port-input-buffer! $tip #vu8(1 2 3)))
  (error? ; not a string
    (set-textual-port-input-buffer! $tip 0))
  (error? ; invalid index
    (set-textual-port-input-index! $tip "hello"))
  (error? ; invalid index
    (set-textual-port-input-index! $tip -1))
  (error? ; invalid index
    (set-textual-port-input-index! $tip 6))
  (error? ; invalid size
    (set-textual-port-input-size! $tip "hello"))
  (error? ; invalid size
    (set-textual-port-input-size! $tip -1))
  (error? ; invalid size
    (set-textual-port-input-size! $tip 6))
  ; textual output
  (equal?
    (let ([op (make-output-port (lambda args (error #f "oops")) (make-string 10 #\$))])
      (let ([buffer0 (string-copy (textual-port-output-buffer op))]
            [index0 (textual-port-output-index op)]
            [size0 (textual-port-output-size op)]
            [count0 (textual-port-output-count op)])
        (display "hey!" op)
        (list
          (list buffer0 index0 size0 count0)
          (list
            (textual-port-output-buffer op)
            (textual-port-output-index op)
            (textual-port-output-size op)
            (textual-port-output-count op)))))
    '(("$$$$$$$$$$" 0 10 10)
      ("hey!$$$$$$" 4 10 6)))
  (equal?
    (let-values ([(op get) (open-string-output-port)])
      (let ([buffer (make-string 8 #\$)])
        (set-textual-port-output-buffer! op buffer)
        (let ([buffer0 (string-copy (textual-port-output-buffer op))]
              [index0 (textual-port-output-index op)]
              [size0 (textual-port-output-size op)]
              [count0 (textual-port-output-count op)])
          (display "yo!" op)
          (list
            buffer
            (list buffer0 index0 size0 count0)
            (list
              (textual-port-output-buffer op)
              (textual-port-output-index op)
              (textual-port-output-size op)
              (textual-port-output-count op))))))
    '("yo!$$$$$"
      ("$$$$$$$$" 0 8 8)
      ("yo!$$$$$" 3 8 5)))
  (equal?
    (let-values ([(op get) (open-string-output-port)])
      (let ([buffer (make-string 8 #\$)])
        (set-textual-port-output-buffer! op buffer)
        (let ([buffer0 (string-copy (textual-port-output-buffer op))]
              [index0 (textual-port-output-index op)]
              [size0 (textual-port-output-size op)]
              [count0 (textual-port-output-count op)])
          (display "yo" op)
          (set-textual-port-output-buffer! op (string #\a #\b #\c))
          (display "!?" op)
          (list
            buffer
            (list buffer0 index0 size0 count0)
            (list
              (textual-port-output-buffer op)
              (textual-port-output-index op)
              (textual-port-output-size op)
              (textual-port-output-count op))))))
    '("yo$$$$$$"
      ("$$$$$$$$" 0 8 8)
      ("!?c" 2 3 1)))
  (equal?
    (let-values ([(op get) (open-string-output-port)])
      (let ([buffer (make-string 8 #\$)])
        (set-textual-port-output-buffer! op buffer)
        (let ([buffer0 (string-copy (textual-port-output-buffer op))]
              [index0 (textual-port-output-index op)]
              [size0 (textual-port-output-size op)]
              [count0 (textual-port-output-count op)])
          (display "yo" op)
          (set-textual-port-output-index! op 4)
          (display "!?" op)
          (list
            buffer
            (list buffer0 index0 size0 count0)
            (list
              (textual-port-output-buffer op)
              (textual-port-output-index op)
              (textual-port-output-size op)
              (textual-port-output-count op))))))
    '("yo$$!?$$"
      ("$$$$$$$$" 0 8 8)
      ("yo$$!?$$" 6 8 2)))
  (equal?
    (let-values ([(op get) (open-string-output-port)])
      (let ([buffer (make-string 8 #\$)])
        (set-textual-port-output-buffer! op buffer)
        (let ([buffer0 (string-copy (textual-port-output-buffer op))]
              [index0 (textual-port-output-index op)]
              [size0 (textual-port-output-size op)]
              [count0 (textual-port-output-count op)])
          (display "yo" op)
          (set-textual-port-output-size! op 4)
          (display "!?" op)
          (list
            buffer
            (list buffer0 index0 size0 count0)
            (list
              (textual-port-output-buffer op)
              (textual-port-output-index op)
              (textual-port-output-size op)
              (textual-port-output-count op))))))
    '("!?$$$$$$"
      ("$$$$$$$$" 0 8 8)
      ("!?$$$$$$" 2 4 2)))
  (error? ; not a textual output port
    (textual-port-output-buffer $tip))
  (error? ; not a textual output port
    (textual-port-output-buffer $bip))
  (error? ; not a textual output port
    (textual-port-output-buffer $bop))
  (error? ; not a textual output port
    (textual-port-output-buffer 75))
  (error? ; not a textual output port
    (textual-port-output-index $tip))
  (error? ; not a textual output port
    (textual-port-output-index $bip))
  (error? ; not a textual output port
    (textual-port-output-index $bop))
  (error? ; not a textual output port
    (textual-port-output-index 75))
  (error? ; not a textual output port
    (textual-port-output-size $tip))
  (error? ; not a textual output port
    (textual-port-output-size $bip))
  (error? ; not a textual output port
    (textual-port-output-size $bop))
  (error? ; not a textual output port
    (textual-port-output-size 75))
  (error? ; not a textual output port
    (textual-port-output-count $tip))
  (error? ; not a textual output port
    (textual-port-output-count $bip))
  (error? ; not a textual output port
    (textual-port-output-count $bop))
  (error? ; not a textual output port
    (textual-port-output-count 75))
  (error? ; not a textual output port
    (set-textual-port-output-buffer! $tip ""))
  (error? ; not a textual output port
    (set-textual-port-output-buffer! $bip ""))
  (error? ; not a textual output port
    (set-textual-port-output-buffer! $bop ""))
  (error? ; not a textual output port
    (set-textual-port-output-buffer! 75 ""))
  (error? ; not a textual output port
    (set-textual-port-output-index! $tip 0))
  (error? ; not a textual output port
    (set-textual-port-output-index! $bip 0))
  (error? ; not a textual output port
    (set-textual-port-output-index! $bop 0))
  (error? ; not a textual output port
    (set-textual-port-output-index! 75 0))
  (error? ; not a textual output port
    (set-textual-port-output-size! $tip 0))
  (error? ; not a textual output port
    (set-textual-port-output-size! $bip 0))
  (error? ; not a textual output port
    (set-textual-port-output-size! $bop 0))
  (error? ; not a textual output port
    (set-textual-port-output-size! 75 0))
  (error? ; not a string
    (set-textual-port-output-buffer! $top #vu8(1 2 3)))
  (error? ; not a string
    (set-textual-port-output-buffer! $top 0))
  (error? ; invalid index
    (set-textual-port-output-index! $top "hello"))
  (error? ; invalid index
    (set-textual-port-output-index! $top -1))
  (error? ; invalid index
    (set-textual-port-output-index! $top 6))
  (error? ; invalid size
    (set-textual-port-output-size! $top "hello"))
  (error? ; invalid size
    (set-textual-port-output-size! $top -1))
  (error? ; invalid size
    (set-textual-port-output-size! $top 6))
  ; binary input
  (equal?
    (let ([ip (open-bytevector-input-port (string->utf8 "hello"))])
      (let ([buffer0 (binary-port-input-buffer ip)]
            [index0 (binary-port-input-index ip)]
            [size0 (binary-port-input-size ip)]
            [count0 (binary-port-input-count ip)])
        (get-u8 ip)
        (list
          (list buffer0 index0 size0 count0)
          (list
            (binary-port-input-buffer ip)
            (binary-port-input-index ip)
            (binary-port-input-size ip)
            (binary-port-input-count ip)))))
    `((,(string->utf8 "hello") 0 5 5) (,(string->utf8 "hello") 1 5 4)))
  (equal?
    (let ([ip (open-bytevector-input-port (string->utf8 "hello"))])
      (let ([buffer0 (binary-port-input-buffer ip)]
            [index0 (binary-port-input-index ip)]
            [size0 (binary-port-input-size ip)]
            [count0 (binary-port-input-count ip)])
        (get-u8 ip)
        (set-binary-port-input-buffer! ip (string->utf8 "goodbye"))
        (get-u8 ip)
        (list
          (list buffer0 index0 size0 count0)
          (list
            (binary-port-input-buffer ip)
            (binary-port-input-index ip)
            (binary-port-input-size ip)
            (binary-port-input-count ip)))))
    `((,(string->utf8 "hello") 0 5 5) (,(string->utf8 "goodbye") 1 7 6)))
  (equal?
    (let ([ip (open-bytevector-input-port (string->utf8 "hello"))])
      (let ([buffer0 (binary-port-input-buffer ip)]
            [index0 (binary-port-input-index ip)]
            [size0 (binary-port-input-size ip)]
            [count0 (binary-port-input-count ip)])
        (get-u8 ip)
        (set-binary-port-input-size! ip 3)
        (get-u8 ip)
        (list
          (list buffer0 index0 size0 count0)
          (list
            (binary-port-input-buffer ip)
            (binary-port-input-index ip)
            (binary-port-input-size ip)
            (binary-port-input-count ip)))))
    `((,(string->utf8 "hello") 0 5 5) (,(string->utf8 "hello") 1 3 2)))
  (equal?
    (let ([ip (open-bytevector-input-port (string->utf8 "hello"))])
      (let ([buffer0 (binary-port-input-buffer ip)]
            [index0 (binary-port-input-index ip)]
            [size0 (binary-port-input-size ip)]
            [count0 (binary-port-input-count ip)])
        (get-u8 ip)
        (set-binary-port-input-index! ip 3)
        (get-u8 ip)
        (list
          (list buffer0 index0 size0 count0)
          (list
            (binary-port-input-buffer ip)
            (binary-port-input-index ip)
            (binary-port-input-size ip)
            (binary-port-input-count ip)))))
    `((,(string->utf8 "hello") 0 5 5) (,(string->utf8 "hello") 4 5 1)))
  (error? ; not a binary input port
    (binary-port-input-buffer $tip))
  (error? ; not a binary input port
    (binary-port-input-buffer $top))
  (error? ; not a binary input port
    (binary-port-input-buffer $bop))
  (error? ; not a binary input port
    (binary-port-input-buffer 75))
  (error? ; not a binary input port
    (binary-port-input-index $tip))
  (error? ; not a binary input port
    (binary-port-input-index $top))
  (error? ; not a binary input port
    (binary-port-input-index $bop))
  (error? ; not a binary input port
    (binary-port-input-index 75))
  (error? ; not a binary input port
    (binary-port-input-size $tip))
  (error? ; not a binary input port
    (binary-port-input-size $top))
  (error? ; not a binary input port
    (binary-port-input-size $bop))
  (error? ; not a binary input port
    (binary-port-input-size 75))
  (error? ; not a binary input port
    (binary-port-input-count $tip))
  (error? ; not a binary input port
    (binary-port-input-count $top))
  (error? ; not a binary input port
    (binary-port-input-count $bop))
  (error? ; not a binary input port
    (binary-port-input-count 75))
  (error? ; not a binary input port
    (set-binary-port-input-buffer! $tip ""))
  (error? ; not a binary input port
    (set-binary-port-input-buffer! $top ""))
  (error? ; not a binary input port
    (set-binary-port-input-buffer! $bop ""))
  (error? ; not a binary input port
    (set-binary-port-input-buffer! 75 ""))
  (error? ; not a binary input port
    (set-binary-port-input-index! $tip 0))
  (error? ; not a binary input port
    (set-binary-port-input-index! $top 0))
  (error? ; not a binary input port
    (set-binary-port-input-index! $bop 0))
  (error? ; not a binary input port
    (set-binary-port-input-index! 75 0))
  (error? ; not a binary input port
    (set-binary-port-input-size! $tip 0))
  (error? ; not a binary input port
    (set-binary-port-input-size! $top 0))
  (error? ; not a binary input port
    (set-binary-port-input-size! $bop 0))
  (error? ; not a binary input port
    (set-binary-port-input-size! 75 0))
  (error? ; not a bytevector
    (set-binary-port-input-buffer! $bip "hello"))
  (error? ; not a bytevector
    (set-binary-port-input-buffer! $bip 0))
  (error? ; invalid index
    (set-binary-port-input-index! $bip #vu8(1 2 3)))
  (error? ; invalid index
    (set-binary-port-input-index! $bip -1))
  (error? ; invalid index
    (set-binary-port-input-index! $bip 6))
  (error? ; invalid size
    (set-binary-port-input-size! $bip #vu8(1 2 3)))
  (error? ; invalid size
    (set-binary-port-input-size! $bip -1))
  (error? ; invalid size
    (set-binary-port-input-size! $bip 6))
  ; binary output
  (equal?
    (let-values ([(op get) (open-bytevector-output-port)])
      (let ([buffer (string->utf8 "hello")])
        (set-binary-port-output-buffer! op buffer)
        (let ([buffer0 (bytevector-copy (binary-port-output-buffer op))]
              [index0 (binary-port-output-index op)]
              [size0 (binary-port-output-size op)]
              [count0 (binary-port-output-count op)])
          (put-u8 op (char->integer #\j))
          (list
            buffer
            (list buffer0 index0 size0 count0)
            (list
              (binary-port-output-buffer op)
              (binary-port-output-index op)
              (binary-port-output-size op)
              (binary-port-output-count op))))))
    `(,(string->utf8 "jello")
      (,(string->utf8 "hello") 0 5 5)
      (,(string->utf8 "jello") 1 5 4)))
  (equal?
    (let-values ([(op get) (open-bytevector-output-port)])
      (let ([buffer (string->utf8 "hello")])
        (set-binary-port-output-buffer! op buffer)
        (let ([buffer0 (bytevector-copy (binary-port-output-buffer op))]
              [index0 (binary-port-output-index op)]
              [size0 (binary-port-output-size op)]
              [count0 (binary-port-output-count op)])
          (put-u8 op (char->integer #\j))
          (set-binary-port-output-buffer! op (bytevector 1 2 3 4 5 6))
          (put-u8 op 31)
          (list
            buffer
            (list buffer0 index0 size0 count0)
            (list
              (binary-port-output-buffer op)
              (binary-port-output-index op)
              (binary-port-output-size op)
              (binary-port-output-count op))))))
    `(,(string->utf8 "jello")
      (,(string->utf8 "hello") 0 5 5)
      (#vu8(31 2 3 4 5 6) 1 6 5)))
  (equal?
    (let-values ([(op get) (open-bytevector-output-port)])
      (let ([buffer (string->utf8 "hello")])
        (set-binary-port-output-buffer! op buffer)
        (let ([buffer0 (bytevector-copy (binary-port-output-buffer op))]
              [index0 (binary-port-output-index op)]
              [size0 (binary-port-output-size op)]
              [count0 (binary-port-output-count op)])
          (put-u8 op (char->integer #\j))
          (set-binary-port-output-index! op 4)
          (put-u8 op (char->integer #\y))
          (list
            buffer
            (list buffer0 index0 size0 count0)
            (list
              (binary-port-output-buffer op)
              (binary-port-output-index op)
              (binary-port-output-size op)
              (binary-port-output-count op))))))
    `(,(string->utf8 "jelly")
      (,(string->utf8 "hello") 0 5 5)
      (,(string->utf8 "jelly") 5 5 0)))
  (equal?
    (let-values ([(op get) (open-bytevector-output-port)])
      (let ([buffer (string->utf8 "hello")])
        (set-binary-port-output-buffer! op buffer)
        (let ([buffer0 (bytevector-copy (binary-port-output-buffer op))]
              [index0 (binary-port-output-index op)]
              [size0 (binary-port-output-size op)]
              [count0 (binary-port-output-count op)])
          (put-u8 op (char->integer #\j))
          (set-binary-port-output-size! op 4)
          (put-u8 op (char->integer #\b))
          (list
            buffer
            (list buffer0 index0 size0 count0)
            (list
              (binary-port-output-buffer op)
              (binary-port-output-index op)
              (binary-port-output-size op)
              (binary-port-output-count op))))))
    `(,(string->utf8 "bello")
      (,(string->utf8 "hello") 0 5 5)
      (,(string->utf8 "bello") 1 4 3)))
  (error? ; not a binary output port
    (binary-port-output-buffer $tip))
  (error? ; not a binary output port
    (binary-port-output-buffer $top))
  (error? ; not a binary output port
    (binary-port-output-buffer $bip))
  (error? ; not a binary output port
    (binary-port-output-buffer 75))
  (error? ; not a binary output port
    (binary-port-output-index $tip))
  (error? ; not a binary output port
    (binary-port-output-index $top))
  (error? ; not a binary output port
    (binary-port-output-index $bip))
  (error? ; not a binary output port
    (binary-port-output-index 75))
  (error? ; not a binary output port
    (binary-port-output-size $tip))
  (error? ; not a binary output port
    (binary-port-output-size $top))
  (error? ; not a binary output port
    (binary-port-output-size $bip))
  (error? ; not a binary output port
    (binary-port-output-size 75))
  (error? ; not a binary output port
    (binary-port-output-count $tip))
  (error? ; not a binary output port
    (binary-port-output-count $top))
  (error? ; not a binary output port
    (binary-port-output-count $bip))
  (error? ; not a binary output port
    (binary-port-output-count 75))
  (error? ; not a binary output port
    (set-binary-port-output-buffer! $tip ""))
  (error? ; not a binary output port
    (set-binary-port-output-buffer! $top ""))
  (error? ; not a binary output port
    (set-binary-port-output-buffer! $bip ""))
  (error? ; not a binary output port
    (set-binary-port-output-buffer! 75 ""))
  (error? ; not a binary output port
    (set-binary-port-output-index! $tip 0))
  (error? ; not a binary output port
    (set-binary-port-output-index! $top 0))
  (error? ; not a binary output port
    (set-binary-port-output-index! $bip 0))
  (error? ; not a binary output port
    (set-binary-port-output-index! 75 0))
  (error? ; not a binary output port
    (set-binary-port-output-size! $tip 0))
  (error? ; not a binary output port
    (set-binary-port-output-size! $top 0))
  (error? ; not a binary output port
    (set-binary-port-output-size! $bip 0))
  (error? ; not a binary output port
    (set-binary-port-output-size! 75 0))
  (error? ; not a string
    (set-binary-port-output-buffer! $bop "hello"))
  (error? ; not a string
    (set-binary-port-output-buffer! $bop 0))
  (error? ; invalid index
    (set-binary-port-output-index! $bop #vu8(1 2 3)))
  (error? ; invalid index
    (set-binary-port-output-index! $bop -1))
  (error? ; invalid index
    (set-binary-port-output-index! $bop 6))
  (error? ; invalid size
    (set-binary-port-output-size! $bop #vu8(1 2 3)))
  (error? ; invalid size
    (set-binary-port-output-size! $bop -1))
  (error? ; invalid size
    (set-binary-port-output-size! $bop 6))
  (begin
    (define $handler-standin (#%$port-handler (open-string-input-port "hi")))
    #t)
  (let ([name "foo"] [ib "hey!"])
    (let ([p (#%$make-textual-input-port name $handler-standin ib)])
      (and (port? p)
           (textual-port? p)
           (not (binary-port? p))
           (input-port? p)
           (not (output-port? p))
           (eq? (port-name p) name)
           (eq? (#%$port-handler p) $handler-standin)
           (eq? (#%$port-info p) #f)
           (eq? (textual-port-input-buffer p) ib)
           (eqv? (textual-port-input-size p) (string-length ib))
           (eqv? (textual-port-input-index p) 0)
           (eqv? (textual-port-input-count p) (string-length ib)))))
  (let ([name "foo"] [info "info"] [ib "hey!"])
    (let ([p (#%$make-textual-input-port name $handler-standin ib info)])
      (and (port? p)
           (textual-port? p)
           (not (binary-port? p))
           (input-port? p)
           (not (output-port? p))
           (eq? (port-name p) name)
           (eq? (#%$port-handler p) $handler-standin)
           (eq? (#%$port-info p) info)
           (eq? (textual-port-input-buffer p) ib)
           (eqv? (textual-port-input-size p) (string-length ib))
           (eqv? (textual-port-input-index p) 0)
           (eqv? (textual-port-input-count p) (string-length ib)))))
  (let ([name "foo"] [ob "hey!"])
    (let ([p (#%$make-textual-output-port name $handler-standin ob)])
      (and (port? p)
           (textual-port? p)
           (not (binary-port? p))
           (not (input-port? p))
           (output-port? p)
           (eq? (port-name p) name)
           (eq? (#%$port-handler p) $handler-standin)
           (eq? (#%$port-info p) #f)
           (eq? (textual-port-output-buffer p) ob)
           (eqv? (textual-port-output-size p) (string-length ob))
           (eqv? (textual-port-output-index p) 0)
           (eqv? (textual-port-output-count p) (string-length ob)))))
  (let ([name "foo"] [info "info"] [ob "hey!"])
    (let ([p (#%$make-textual-output-port name $handler-standin ob info)])
      (and (port? p)
           (textual-port? p)
           (not (binary-port? p))
           (not (input-port? p))
           (output-port? p)
           (eq? (port-name p) name)
           (eq? (#%$port-handler p) $handler-standin)
           (eq? (#%$port-info p) info)
           (eq? (textual-port-output-buffer p) ob)
           (eqv? (textual-port-output-size p) (string-length ob))
           (eqv? (textual-port-output-index p) 0)
           (eqv? (textual-port-output-count p) (string-length ob)))))
  (let ([name "foo"] [ib "hay!"] [ob "hey!"])
    (let ([p (#%$make-textual-input/output-port name $handler-standin ib ob)])
      (and (port? p)
           (textual-port? p)
           (not (binary-port? p))
           (input-port? p)
           (output-port? p)
           (eq? (port-name p) name)
           (eq? (#%$port-handler p) $handler-standin)
           (eq? (#%$port-info p) #f)
           (eq? (textual-port-input-buffer p) ib)
           (eqv? (textual-port-input-size p) (string-length ib))
           (eqv? (textual-port-input-index p) 0)
           (eqv? (textual-port-input-count p) (string-length ib))
           (eq? (textual-port-output-buffer p) ob)
           (eqv? (textual-port-output-size p) (string-length ob))
           (eqv? (textual-port-output-index p) 0)
           (eqv? (textual-port-output-count p) (string-length ob)))))
  (let ([name "foo"] [info "info"] [ib "hay!"] [ob "hey!"])
    (let ([p (#%$make-textual-input/output-port name $handler-standin ib ob info)])
      (and (port? p)
           (textual-port? p)
           (not (binary-port? p))
           (input-port? p)
           (output-port? p)
           (eq? (port-name p) name)
           (eq? (#%$port-handler p) $handler-standin)
           (eq? (#%$port-info p) info)
           (eq? (textual-port-input-buffer p) ib)
           (eqv? (textual-port-input-size p) (string-length ib))
           (eqv? (textual-port-input-index p) 0)
           (eqv? (textual-port-input-count p) (string-length ib))
           (eq? (textual-port-output-buffer p) ob)
           (eqv? (textual-port-output-size p) (string-length ob))
           (eqv? (textual-port-output-index p) 0)
           (eqv? (textual-port-output-count p) (string-length ob)))))
  (let ([name "foo"] [ib #vu8(1 2 3 4)])
    (let ([p (#%$make-binary-input-port name $handler-standin ib)])
      (and (port? p)
           (not (textual-port? p))
           (binary-port? p)
           (input-port? p)
           (not (output-port? p))
           (eq? (port-name p) name)
           (eq? (#%$port-handler p) $handler-standin)
           (eq? (#%$port-info p) #f)
           (eq? (binary-port-input-buffer p) ib)
           (eqv? (binary-port-input-size p) (bytevector-length ib))
           (eqv? (binary-port-input-index p) 0)
           (eqv? (binary-port-input-count p) (bytevector-length ib)))))
  (let ([name "foo"] [info "info"] [ib #vu8(1 2 3 4)])
    (let ([p (#%$make-binary-input-port name $handler-standin ib info)])
      (and (port? p)
           (not (textual-port? p))
           (binary-port? p)
           (input-port? p)
           (not (output-port? p))
           (eq? (port-name p) name)
           (eq? (#%$port-handler p) $handler-standin)
           (eq? (#%$port-info p) info)
           (eq? (binary-port-input-buffer p) ib)
           (eqv? (binary-port-input-size p) (bytevector-length ib))
           (eqv? (binary-port-input-index p) 0)
           (eqv? (binary-port-input-count p) (bytevector-length ib)))))
  (let ([name "foo"] [ob #vu8(1 2 3 4)])
    (let ([p (#%$make-binary-output-port name $handler-standin ob)])
      (and (port? p)
           (not (textual-port? p))
           (binary-port? p)
           (not (input-port? p))
           (output-port? p)
           (eq? (port-name p) name)
           (eq? (#%$port-handler p) $handler-standin)
           (eq? (#%$port-info p) #f)
           (eq? (binary-port-output-buffer p) ob)
           (eqv? (binary-port-output-size p) (bytevector-length ob))
           (eqv? (binary-port-output-index p) 0)
           (eqv? (binary-port-output-count p) (bytevector-length ob)))))
  (let ([name "foo"] [info "info"] [ob #vu8(1 2 3 4)])
    (let ([p (#%$make-binary-output-port name $handler-standin ob info)])
      (and (port? p)
           (not (textual-port? p))
           (binary-port? p)
           (not (input-port? p))
           (output-port? p)
           (eq? (port-name p) name)
           (eq? (#%$port-handler p) $handler-standin)
           (eq? (#%$port-info p) info)
           (eq? (binary-port-output-buffer p) ob)
           (eqv? (binary-port-output-size p) (bytevector-length ob))
           (eqv? (binary-port-output-index p) 0)
           (eqv? (binary-port-output-count p) (bytevector-length ob)))))
  (let ([name "foo"] [ib #vu8(4 3 2 1)] [ob #vu8(1 2 3 4)])
    (let ([p (#%$make-binary-input/output-port name $handler-standin ib ob)])
      (and (port? p)
           (not (textual-port? p))
           (binary-port? p)
           (input-port? p)
           (output-port? p)
           (eq? (port-name p) name)
           (eq? (#%$port-handler p) $handler-standin)
           (eq? (#%$port-info p) #f)
           (eq? (binary-port-input-buffer p) ib)
           (eqv? (binary-port-input-size p) (bytevector-length ib))
           (eqv? (binary-port-input-index p) 0)
           (eqv? (binary-port-input-count p) (bytevector-length ib))
           (eq? (binary-port-output-buffer p) ob)
           (eqv? (binary-port-output-size p) (bytevector-length ob))
           (eqv? (binary-port-output-index p) 0)
           (eqv? (binary-port-output-count p) (bytevector-length ob)))))
  (let ([name "foo"] [info "info"] [ib #vu8(4 3 2 1)] [ob #vu8(1 2 3 4)])
    (let ([p (#%$make-binary-input/output-port name $handler-standin ib ob info)])
      (and (port? p)
           (not (textual-port? p))
           (binary-port? p)
           (input-port? p)
           (output-port? p)
           (eq? (port-name p) name)
           (eq? (#%$port-handler p) $handler-standin)
           (eq? (#%$port-info p) info)
           (eq? (binary-port-input-buffer p) ib)
           (eqv? (binary-port-input-size p) (bytevector-length ib))
           (eqv? (binary-port-input-index p) 0)
           (eqv? (binary-port-input-count p) (bytevector-length ib))
           (eq? (binary-port-output-buffer p) ob)
           (eqv? (binary-port-output-size p) (bytevector-length ob))
           (eqv? (binary-port-output-index p) 0)
           (eqv? (binary-port-output-count p) (bytevector-length ob)))))
 )

(mat file-buffer-size
  (let ([x (file-buffer-size)])
    (and (fixnum? x) (> x 0)))
  (error? (file-buffer-size 1024 15))
  (error? (file-buffer-size 'shoe))
  (error? (file-buffer-size 0))
  (error? (file-buffer-size -15))
  (error? (file-buffer-size (+ (most-positive-fixnum) 1)))
  (error? (file-buffer-size 1024.0))
  (parameterize ([file-buffer-size (* (file-buffer-size) 2)])
    (let ([ip (open-file-input-port prettytest.ss)])
      (let ([n (bytevector-length (binary-port-input-buffer ip))])
        (close-input-port ip)
        (eqv? n (file-buffer-size)))))
)

(mat custom-port-buffer-size
  (let ([x (custom-port-buffer-size)])
    (and (fixnum? x) (> x 0)))
  (error? (custom-port-buffer-size 1024 15))
  (error? (custom-port-buffer-size 'shoe))
  (error? (custom-port-buffer-size 0))
  (error? (custom-port-buffer-size -15))
  (error? (custom-port-buffer-size (+ (most-positive-fixnum) 1)))
  (error? (custom-port-buffer-size 1024.0))
  (parameterize ([custom-port-buffer-size (* (custom-port-buffer-size) 2)])
    (let ([q #f])
      (let ([ip (make-custom-textual-input-port "foo"
                  (lambda (str s c) (set! q c) 0)
                  #f #f #f)])
        (read-char ip)
        (= q (custom-port-buffer-size)))))
)

(mat compress-parameters
  (error? ; unsupported format
    (compress-format 'foo))
  (error? ; unsupported format
    (compress-format "gzip"))
  (eq? (compress-format) 'lz4)
  (eq? (parameterize ([compress-format 'gzip]) (compress-format)) 'gzip)
  (eq? (parameterize ([compress-format 'lz4]) (compress-format)) 'lz4)
  (error? ; unsupported level
    (compress-level 'foo))
  (error? ; unsupported level
    (compress-level 1))
  (eq? (compress-level) 'medium)
  (eq? (parameterize ([compress-level 'low]) (compress-level)) 'low)
  (eq? (parameterize ([compress-level 'medium]) (compress-level)) 'medium)
  (eq? (parameterize ([compress-level 'high]) (compress-level)) 'high)
  (eq? (parameterize ([compress-level 'maximum]) (compress-level)) 'maximum)
  (begin
    (define (fnlength ifn) (call-with-port (open-file-input-port ifn) port-length))
    (define (compress-file ifn ofn fmt lvl)
      (call-with-port (open-file-input-port ifn)
        (lambda (ip)
          (call-with-port (parameterize ([compress-format fmt] [compress-level lvl])
                            (open-file-output-port ofn (file-options compressed replace)))
            (lambda (op) (put-bytevector op (get-bytevector-all ip))))))
      (fnlength ofn))
    (define (compress-file-test fmt)
      (let ([orig (fnlength prettytest.ss)]
            [low (compress-file prettytest.ss "testfile.ss" fmt 'low)]
            [medium (compress-file prettytest.ss "testfile.ss" fmt 'medium)]
            [high (compress-file prettytest.ss "testfile.ss" fmt 'high)]
            [maximum (compress-file prettytest.ss "testfile.ss" fmt 'maximum)])
        (define-syntax test1
          (syntax-rules ()
            [(_ level)
             (unless (< level orig) (errorf #f "~s ~s did not compress" fmt 'level))]))
        (define-syntax test2
          (syntax-rules ()
            [(_ level1 level2)
             (unless (<= level2 level1) (errorf #f "~s ~s did worse than ~s" fmt 'level2 'level1))]))
        (test1 low)
        (test1 medium)
        (test1 high)
        (test1 maximum)
        (test2 low medium)
        (test2 medium high)
        (test2 high maximum)
        (unless (< maximum low) (errorf #f "~s maximum didn't do better than low" fmt))))
    (compress-file-test 'lz4)
    (compress-file-test 'gzip)
    #t)
)

(mat compression
  (parameters [compress-format 'gzip 'lz4] [compress-level 'low 'medium 'high 'maximum])
  (and (memq (compress-format) '(gzip lz4)) #t)
  (and (memq (compress-level) '(low medium high maximum)) #t)
  (let ()
    (define cp
      (lambda (src dst)
        (define buf-size 4096)
        (let ([buf (make-bytevector buf-size)])
          (call-with-port dst
            (lambda (op)
              (call-with-port src
                (lambda (ip)
                  (let loop ()
                    (let ([n (get-bytevector-n! ip buf 0 buf-size)])
                      (unless (eof-object? n)
                        (put-bytevector op buf 0 n)
                        (loop)))))))))))

    (define cmp
      (lambda (src1 src2)
        (define buf-size 4096)
        (let ([buf1 (make-bytevector buf-size)]
              [buf2 (make-bytevector buf-size)])
          (call-with-port src1
            (lambda (ip1)
              (call-with-port src2
                (lambda (ip2)
                  (let loop ()
                    (let ([n1 (get-bytevector-n! ip1 buf1 0 buf-size)]
                          [n2 (get-bytevector-n! ip2 buf2 0 buf-size)])
                      (if (eof-object? n1)
                          (eof-object? n2)
                          (and (= n1 n2)
                               (let test ([i 0])
                                 (or (= i n1)
                                     (and (= (bytevector-u8-ref buf1 i)
                                             (bytevector-u8-ref buf2 i))
                                          (test (+ 1 i)))))
                               (loop))))))))))))
    (and
     (cmp (open-file-input-port prettytest.ss)
          (open-file-input-port prettytest.ss))
     (cmp (open-file-input-port prettytest.ss (file-options compressed))
          (open-file-input-port prettytest.ss))
     (cmp (open-file-input-port prettytest.ss)
          (open-file-input-port prettytest.ss (file-options compressed)))
     (cmp (open-file-input-port prettytest.ss (file-options compressed))
          (open-file-input-port prettytest.ss (file-options compressed)))
     (begin
       (cp (open-file-input-port prettytest.ss)
           (open-file-output-port "testfile.ss" (file-options replace compressed)))
       #t)
     (cmp (open-file-input-port "testfile.ss" (file-options compressed))
          (open-file-input-port prettytest.ss))
     (not (cmp (open-file-input-port "testfile.ss")
               (open-file-input-port prettytest.ss)))
     (begin
       (cp (open-file-input-port prettytest.ss)
           (open-file-output-port "testfile.ss" (file-options no-fail no-truncate append compressed)))
       #t)
     (not (cmp (open-file-input-port "testfile.ss" (file-options compressed))
               (open-file-input-port prettytest.ss)))))
  ; test workaround for bogus gzclose error return for empty input files
  (and
   (eqv? (call-with-port
          (open-file-output-port "testfile.ss" (file-options replace))
          (lambda (x) (void)))
         (void))
   (eof-object? (call-with-port
                 (open-file-input-port "testfile.ss" (file-options compressed))
                 get-u8)))
  (begin
    (let ([op (open-file-output-port "testfile.ss" (file-options replace))])
      (put-bytevector op #vu8(#x23 #x88 #x09 #x72 #xf3 #x72))
      (port-file-compressed! op)
      (put-bytevector op #vu8(#x93 #x21 #x88 #xe7 #x67))
      (let ([op (transcoded-port op (native-transcoder))])
        (display "hello!\n" op)
        (close-port op)))
    #t)
  (equal?
    (let ([ip (open-file-input-port "testfile.ss")])
      (let ([bv1 (get-bytevector-n ip 6)])
        (port-file-compressed! ip)
        (let ([bv2 (get-bytevector-n ip 5)])
          (let ([ip (transcoded-port ip (native-transcoder))])
            (let ([s (get-string-all ip)])
              (close-port ip)
              (list bv1 bv2 s))))))
    '(#vu8(#x23 #x88 #x09 #x72 #xf3 #x72)
      #vu8(#x93 #x21 #x88 #xe7 #x67)
      "hello!\n"))
  (not
    (equal?
      (let ([ip (open-file-input-port "testfile.ss")])
        (let ([bv1 (get-bytevector-n ip 6)])
          (let ([bv2 (get-bytevector-n ip 5)])
            (close-port ip)
            (list bv1 bv2))))
      '(#vu8(#x23 #x88 #x09 #x72 #xf3 #x72)
        #vu8(#x93 #x21 #x88 #xe7 #x67))))
  (begin
    (let ([op (open-file-output-port "testfile.ss" (file-options replace) (buffer-mode block) (native-transcoder))])
      (put-string op "uncompressed string")
      (port-file-compressed! op)
      (put-string op "compressed string")
      (close-port op))
    #t)
  (equal?
    (let ([ip (open-file-input-port "testfile.ss" (file-options) (buffer-mode block) (native-transcoder))])
      (let ([s1 (get-string-n ip (string-length "uncompressed string"))])
        (port-file-compressed! ip)
        (let ([s2 (get-string-all ip)])
          (close-port ip)
          (list s1 s2))))
    '("uncompressed string" "compressed string"))
  (error? ; not a file port
    (call-with-string-output-port port-file-compressed!))
  (error? ; input/output ports aren't supported
    (let ([iop (open-file-input/output-port "testfile.ss" (file-options replace))])
      (guard (c [else (close-port iop) (raise c)])
        (port-file-compressed! iop))))
  (begin
    (let ([op (open-file-output-port "testfile.ss" (file-options compressed replace) (buffer-mode block) (native-transcoder))])
      (port-file-compressed! op)
      (put-string op "compressed string")
      (close-port op))
    #t)
  (equal?
    (let ([ip (open-file-input-port "testfile.ss" (file-options compressed) (buffer-mode block) (native-transcoder))])
      (port-file-compressed! ip)
      (let ([s (get-string-all ip)])
        (close-port ip)
        s))
    '"compressed string")
)

(mat bytevector-input-port
  (error? ; incorrect number of arguments
    (open-bytevector-input-port))
  (error? ; not a bytevector
    (open-bytevector-input-port '#(1 2 3 4)))
  (error? ; none is not a transcoder
    (open-bytevector-input-port #vu8(1 2 3 4) 'none))
  (error? ; incorrect number of arguments
    (open-bytevector-input-port #vu8(1 2 3 4) #f 'none))
  (let ()
    (define x (open-bytevector-input-port #vu8(1 2 3 4)))
    (and (eq? (get-u8 x) 1)
         (eq? (get-u8 x) 2)
         (eq? (get-u8 x) 3)
         (eq? (get-u8 x) 4)
         (eq? (get-u8 x) (eof-object))))
  (let ()
    (define x (open-bytevector-input-port #vu8(1 2 3 4)))
    (and (port-has-port-position? x)
         (eq? (port-position x) 0)
         (eq? (get-u8 x) 1)
         (eq? (port-position x) 1)
         (eq? (get-u8 x) 2)
         (eq? (port-position x) 2)
         (eq? (get-u8 x) 3)
         (eq? (port-position x) 3)
         (eq? (get-u8 x) 4)
         (eq? (port-position x) 4)
         (eq? (get-u8 x) #!eof)
         (eq? (port-position x) 4)
         (eq? (get-u8 x) #!eof)
         (eq? (port-position x) 4)
         (eq? (get-u8 x) #!eof)
         (eq? (port-position x) 4)))
  (let ()
    (define x (open-bytevector-input-port #vu8(1 2 3 4)))
    (and (port-has-set-port-position!? x)
         (eq? (port-position x) 0)
         (eq? (get-u8 x) 1)
         (eq? (port-position x) 1)
         (eq? (get-u8 x) 2)
         (eq? (port-position x) 2)
         (begin (set-port-position! x 0) #t)
         (eq? (get-u8 x) 1)
         (begin (set-port-position! x 4) #t)
         (eq? (get-u8 x) #!eof)))
  (error? (set-port-position! (open-bytevector-input-port #vu8(1 2 3 4)) -1))
  (error? (set-port-position! (open-bytevector-input-port #vu8(1 2 3 4)) 5))

  (let ()
    (define x (open-bytevector-input-port #vu8(1 2 3 4)))
    (and (eq? (lookahead-u8 x) 1)
         (eq? (lookahead-u8 x) 1)
         (eq? (lookahead-u8 x) 1)
         (eq? (get-u8 x) 1)
         (eq? (lookahead-u8 x) 2)
         (eq? (get-u8 x) 2)
         (eq? (lookahead-u8 x) 3)
         (eq? (get-u8 x) 3)
         (eq? (lookahead-u8 x) 4)
         (eq? (get-u8 x) 4)
         (eq? (lookahead-u8 x) #!eof)
         (eq? (get-u8 x) #!eof)
         (eq? (lookahead-u8 x) #!eof)
         (eq? (get-u8 x) #!eof)))
  (eq? (buffer-mode none) 'none)
  (eq? (buffer-mode line) 'line)
  (eq? (buffer-mode block) 'block)
  (error? (buffer-mode bar))
  (error? (buffer-mode 'none))
  (eq? (buffer-mode? 'none) #t)
  (eq? (buffer-mode? 'line) #t)
  (eq? (buffer-mode? 'block) #t)
  (eq? (buffer-mode? 'foo) #f)
)

(mat bytevector-output-port
  (error? ; not a transcoder
    (open-bytevector-output-port 'oops))
  (error? ; incorrect number of arguments
    (open-bytevector-output-port #f 'none))
)

(mat custom-binary-ports
  (begin
    (define $cp-ip
      (let ([pos 0])
        (make-custom-binary-input-port "foo"
          (lambda (bv s c)
            (let loop ([i s])
              (unless (eq? i (+ s c))
                (bytevector-u8-set! bv i (modulo (+ pos i) 256))
                (loop (+ 1 i))))
            (set! pos (+ pos c))
            c)
          (lambda () pos)
          (lambda (x) (set! pos x))
          #f)))
    #t)
  (eq? (port-position $cp-ip) 0)
  (error? ; cannot unget
    (unget-u8 $cp-ip 255))
  (begin (unget-u8 $cp-ip (eof-object)) #t)
  (port-eof? $cp-ip)
  (eof-object? (lookahead-u8 $cp-ip))
  (eof-object? (get-u8 $cp-ip))
  (equal?
    (get-bytevector-n $cp-ip 10)
    #vu8(0 1 2 3 4 5 6 7 8 9))
  (eqv? (port-position $cp-ip) 10)
  (eqv? (get-u8 $cp-ip) 10)
  (begin (set-port-position! $cp-ip 256000) #t)
  (eqv? (get-u8 $cp-ip) 0)
  (eqv? (port-position $cp-ip) 256001)
  (error? ; not a binary output port
    (put-u8 $cp-ip 255))
  (not (port-has-port-length? $cp-ip))
  (not (port-has-set-port-length!? $cp-ip))
  (not (port-has-port-nonblocking?? $cp-ip))
  (not (port-has-set-port-nonblocking!? $cp-ip))
  (error? ; not supported
    (port-length $cp-ip))
  (error? ; not supported
    (set-port-length! $cp-ip 50))
  (error? ; not supported
    (port-nonblocking? $cp-ip))
  (error? ; not supported
    (set-port-nonblocking! $cp-ip #t))
  (error? ; not supported
    (set-port-nonblocking! $cp-ip #f))
  (begin
    (define $cp-op
      (let ([pos 0])
        (make-custom-binary-output-port "foo"
          (lambda (bv s c)
            (set! pos (+ pos c))
            (printf "write ~s\n" c)
            c)
          (lambda () pos)
          (lambda (x) (set! pos x))
          (lambda () (printf "closed\n")))))
    #t)
  (eq? (port-position $cp-op) 0)
  (error? ; not a binary input port
    (unget-u8 $cp-op 255))
  (not (port-has-port-length? $cp-op))
  (not (port-has-set-port-length!? $cp-op))
  (not (port-has-port-nonblocking?? $cp-op))
  (not (port-has-set-port-nonblocking!? $cp-op))
  (error? ; not supported
    (port-length $cp-op))
  (error? ; not supported
    (set-port-length! $cp-op 50))
  (error? ; not supported
    (port-nonblocking? $cp-op))
  (error? ; not supported
    (set-port-nonblocking! $cp-op #t))
  (error? ; not supported
    (set-port-nonblocking! $cp-op #f))
  (begin (put-u8 $cp-op 255) #t)
  (eqv? (port-position $cp-op) 1)
  (begin (set-port-position! $cp-op 17) #t)
  (equal?
    (with-output-to-string
      (lambda ()
        (put-bytevector $cp-op #vu8(17 18 19 20))
        (put-bytevector $cp-op #vu8(20 21 22 23 24 25) 1)
        (put-bytevector $cp-op #vu8(24 25 26 27 28 29) 1 4)))
    "")
  (equal? ; in our current implementation...
    (with-output-to-string
      (lambda ()
        (printf "pos = ~s\n" (port-position $cp-op))))
    "pos = 30\n")
  (equal? ; ... actual flush won't happen until here
    (with-output-to-string
      (lambda ()
        (r6rs:flush-output-port $cp-op)))
    "write 13\n")
  (equal?
    (with-output-to-string
      (lambda ()
        (printf "pos = ~s\n" (port-position $cp-op))))
    "pos = 30\n")
  (equal?
    (with-output-to-string
      (lambda ()
        (put-bytevector $cp-op #vu8(17 18 19 20))
        (put-bytevector $cp-op #vu8(20 21 22 23 24 25) 1)
        (put-bytevector $cp-op #vu8(24 25 26 27 28 29) 1 4)))
    "")
  (equal?
    (with-output-to-string
      (lambda ()
        (close-port $cp-op)))
    "write 13\nclosed\n")
  (error? ; closed
    (put-u8 $cp-op 0))
  (error? ; closed
    (put-bytevector $cp-op #vu8(3)))
  (error? ; closed
    (r6rs:flush-output-port $cp-op))
  (begin
    (define $cp-iop
      (let ([pos 0])
        (make-custom-binary-input/output-port "foo"
          (lambda (bv s c)
            (let loop ([i s])
              (unless (eq? i (+ s c))
                (bytevector-u8-set! bv i (modulo (+ pos i) 256))
                (loop (+ 1 i))))
            (set! pos (+ pos c))
            c)
          (lambda (bv s c)
            (set! pos (+ pos c))
            (printf "write ~s\n" c)
            c)
          (lambda () pos)
          (lambda (x) (set! pos x))
          (lambda () (printf "closed\n")))))
    #t)
  (eq? (port-position $cp-iop) 0)
  (error? ; cannot unget
    (unget-u8 $cp-iop 255))
  (begin (unget-u8 $cp-iop (eof-object)) #t)
  (port-eof? $cp-iop)
  (eof-object? (lookahead-u8 $cp-iop))
  (eof-object? (get-u8 $cp-iop))
  (equal?
    (get-bytevector-n $cp-iop 10)
    #vu8(0 1 2 3 4 5 6 7 8 9))
  (eqv? (port-position $cp-iop) 10)
  (eqv? (lookahead-u8 $cp-iop) 10)
  (eqv? (get-u8 $cp-iop) 10)
  (begin (set-port-position! $cp-iop 256000) #t)
  (eqv? (get-u8 $cp-iop) 0)
  (eqv? (port-position $cp-iop) 256001)
  (not (port-has-port-length? $cp-iop))
  (not (port-has-set-port-length!? $cp-iop))
  (not (port-has-port-nonblocking?? $cp-iop))
  (not (port-has-set-port-nonblocking!? $cp-iop))
  (error? ; not supported
    (port-length $cp-iop))
  (error? ; not supported
    (set-port-length! $cp-iop 50))
  (error? ; not supported
    (port-nonblocking? $cp-iop))
  (error? ; not supported
    (set-port-nonblocking! $cp-iop #t))
  (error? ; not supported
    (set-port-nonblocking! $cp-iop #f))
  (begin (put-u8 $cp-iop 255) #t)
  (eqv? (port-position $cp-iop) 256002)
  (begin (set-port-position! $cp-iop 17) #t)
  (equal?
    (with-output-to-string
      (lambda ()
        (put-bytevector $cp-iop #vu8(17 18 19 20))
        (put-bytevector $cp-iop #vu8(20 21 22 23 24 25) 1)
        (put-bytevector $cp-iop #vu8(24 25 26 27 28 29) 1 4)))
    "")
  (equal? ; in our current implementation...
    (with-output-to-string
      (lambda ()
        (printf "pos = ~s\n" (port-position $cp-iop))))
    "pos = 30\n")
  (equal? ; ... actual flush won't happen until here
    (with-output-to-string
      (lambda ()
        (r6rs:flush-output-port $cp-iop)))
    "write 13\n")
  (equal?
    (with-output-to-string
      (lambda ()
        (printf "pos = ~s\n" (port-position $cp-iop))))
    "pos = 30\n")
  (equal?
    (with-output-to-string
      (lambda ()
        (put-bytevector $cp-iop #vu8(17 18 19 20))
        (put-bytevector $cp-iop #vu8(20 21 22 23 24 25) 1)
        (put-bytevector $cp-iop #vu8(24 25 26 27 28 29) 1 4)))
    "")
  (equal?
    (with-output-to-string
      (lambda ()
        (close-port $cp-iop)))
    "write 13\nclosed\n")
  (error? ; closed
    (put-u8 $cp-iop 0))
  (error? ; closed
    (put-bytevector $cp-iop #vu8(3)))
  (error? ; closed
    (r6rs:flush-output-port $cp-iop))

  (begin
    (define $cp-iop
      (let ([pos 0])
        (make-custom-binary-input/output-port "foo"
          (lambda (bv s c)
            (let loop ([i s])
              (unless (eq? i (+ s c))
                (bytevector-u8-set! bv i (modulo (+ pos i) 256))
                (loop (+ 1 i))))
            (set! pos (+ pos c))
            c)
          (lambda (bv s c)
            (set! pos (+ pos c))
            (printf "write ~s\n" c)
            c)
          #f
          (lambda (x) (set! pos x))
          (lambda () (printf "closed\n")))))
    #t)
  (not (port-has-port-position? $cp-iop))
  (error? ; operation not supported
    (port-position $cp-iop))
  (begin
    (guard (c [(warning? c) (errorf #f "unexpected warning ~s" c)])
      (put-u8 $cp-iop 255))
    #t)
  (eqv? (get-u8 $cp-iop) 1)
  (custom-port-warning? ; can't determine position for write
    (put-u8 $cp-iop 255))
  (begin (set-port-position! $cp-iop 50) #t)
  (begin
    (guard (c [(warning? c) (errorf #f "unexpected warning ~s" c)])
      (put-u8 $cp-iop 255))
    #t)
  (eqv? (get-u8 $cp-iop) 51)
  (custom-port-warning? ; can't determine position for write
    (put-bytevector $cp-iop #vu8(17)))

  (begin
    (define $cp-iop
      (let ([pos 0])
        (make-custom-binary-input/output-port "foo"
          (lambda (bv s c)
            (let loop ([i s])
              (unless (eq? i (+ s c))
                (bytevector-u8-set! bv i (modulo (+ pos i) 256))
                (loop (+ 1 i))))
            (set! pos (+ pos c))
            c)
          (lambda (bv s c)
            (set! pos (+ pos c))
            (printf "write ~s\n" c)
            c)
          (lambda () pos)
          #f
          (lambda () (printf "closed\n")))))
    #t)
  (not (port-has-set-port-position!? $cp-iop))
  (error? ; operation not supported
    (set-port-position! $cp-iop 3))
  (begin
    (guard (c [(warning? c) (errorf #f "unexpected warning ~s" c)])
      (put-u8 $cp-iop 255))
    #t)
  (eqv? (get-u8 $cp-iop) 1)
  (custom-port-warning? ; can't set position for write
   ; convoluted because we want warning to return normally so that operation
   ; is completed
    (let ([hit? #f])
      (with-exception-handler
        (lambda (c) (if (warning? c) (set! hit? c) (raise c)))
        (lambda () (put-u8 $cp-iop 255)))
      (when hit? (raise hit?))))
  (begin
    (guard (c [(warning? c) (errorf #f "unexpected warning ~s" c)])
      (put-u8 $cp-iop 255))
    #t)
  (begin (get-u8 $cp-iop) #t) ; position undefined, so value undefined
  (custom-port-warning? ; can't set position for write
    (put-bytevector $cp-iop #vu8(17)))

  (begin
    (define $cp-iop
      (let ([pos 0])
        (make-custom-binary-input/output-port "foo"
          (lambda (bv s c)
            (let loop ([i s])
              (unless (eq? i (+ s c))
                (bytevector-u8-set! bv i (modulo (+ pos i) 256))
                (loop (+ 1 i))))
            (set! pos (+ pos c))
            c)
          (lambda (bv s c)
            (set! pos (+ pos c))
            (printf "write ~s\n" c)
            c)
          #f
          #f
          (lambda () (printf "closed\n")))))
    #t)
  (not (port-has-port-position? $cp-iop))
  (error? ; operation not supported
    (port-position $cp-iop))
  (not (port-has-set-port-position!? $cp-iop))
  (error? ; operation not supported
    (set-port-position! $cp-iop 3))
  (begin
    (guard (c [(warning? c) (errorf #f "unexpected warning ~s" c)])
      (put-u8 $cp-iop 255))
    #t)
  (eqv? (get-u8 $cp-iop) 1)
  (custom-port-warning? ; can't determine position for write
   ; convoluted because we want warning to return normally so that operation
   ; is completed
    (let ([hit? #f])
      (with-exception-handler
        (lambda (c) (if (warning? c) (set! hit? c) (raise c)))
        (lambda () (put-u8 $cp-iop 255)))
      (when hit? (raise hit?))))
  (begin
    (guard (c [(warning? c) (errorf #f "unexpected warning ~s" c)])
      (put-u8 $cp-iop 255))
    #t)
  (begin (get-u8 $cp-iop) #t) ; position undefined, so value undefined
  (custom-port-warning? ; can't determine position for write
    (put-bytevector $cp-iop #vu8(17)))
)

(mat custom-textual-ports
  (begin
    (define $cp-ip
      (let ([pos 0] [chars "0123456789abcdefghijklmnopqrstuvwxyz"])
        (make-custom-textual-input-port "foo"
          (lambda (str s c)
            (let loop ([i s])
              (unless (eq? i (+ s c))
                (string-set! str i (string-ref chars (modulo (+ pos i) 36)))
                (loop (+ 1 i))))
            (set! pos (+ pos c))
            c)
          (lambda () pos)
          (lambda (x) (set! pos x))
          #f)))
    #t)
  (eq? (port-position $cp-ip) 0)
  (error? ; cannot unget
    (unget-char $cp-ip #\q))
  (begin (unget-char $cp-ip (eof-object)) #t)
  (port-eof? $cp-ip)
  (eof-object? (lookahead-char $cp-ip))
  (eof-object? (get-char $cp-ip))
  (equal?
    (get-string-n $cp-ip 10)
    "0123456789")
  (eqv? (port-position $cp-ip) 10)
  (eqv? (get-char $cp-ip) #\a)
  (begin (set-port-position! $cp-ip 36000) #t)
  (eqv? (get-char $cp-ip) #\0)
  (custom-port-warning? (port-position $cp-ip))
  (error? ; not a textual output port
    (put-char $cp-ip #\a))
  (not (port-has-port-length? $cp-ip))
  (not (port-has-set-port-length!? $cp-ip))
  (not (port-has-port-nonblocking?? $cp-ip))
  (not (port-has-set-port-nonblocking!? $cp-ip))
  (error? ; not supported
    (port-length $cp-ip))
  (error? ; not supported
    (set-port-length! $cp-ip 50))
  (error? ; not supported
    (port-nonblocking? $cp-ip))
  (error? ; not supported
    (set-port-nonblocking! $cp-ip #t))
  (error? ; not supported
    (set-port-nonblocking! $cp-ip #f))

  (begin
    (define $cp-op
      (let ([pos 0])
        (make-custom-textual-output-port "foo"
          (lambda (str s c)
            (set! pos (+ pos c))
            (printf "write ~s\n" c)
            c)
          (lambda () pos)
          (lambda (x) (set! pos x))
          (lambda () (printf "closed\n")))))
    #t)
  (eq? (port-position $cp-op) 0)
  (error? ; not a textual output port
    (unget-char $cp-op 255))
  (not (port-has-port-length? $cp-op))
  (not (port-has-set-port-length!? $cp-op))
  (not (port-has-port-nonblocking?? $cp-op))
  (not (port-has-set-port-nonblocking!? $cp-op))
  (error? ; not supported
    (port-length $cp-op))
  (error? ; not supported
    (set-port-length! $cp-op 50))
  (error? ; not supported
    (port-nonblocking? $cp-op))
  (error? ; not supported
    (set-port-nonblocking! $cp-op #t))
  (error? ; not supported
    (set-port-nonblocking! $cp-op #f))
  (begin (put-char $cp-op #\$) #t)
  (eqv? (port-position $cp-op) 1)
  (begin (set-port-position! $cp-op 17) #t)
  (equal?
    (with-output-to-string
      (lambda ()
        (put-string $cp-op "abcd")
        (put-string $cp-op "defghi" 1)
        (put-string $cp-op "hijklm" 1 4)))
    "")
  (equal? ; in our current implementation...
    (with-output-to-string
      (lambda ()
        (printf "pos = ~s\n" (port-position $cp-op))))
    "write 13\npos = 30\n")
  (equal?
    (with-output-to-string
      (lambda ()
        (printf "pos = ~s\n" (port-position $cp-op))))
    "pos = 30\n")
  (equal?
    (with-output-to-string
      (lambda ()
        (put-string $cp-op "abcd")
        (put-string $cp-op "defghi" 1)
        (put-string $cp-op "hijklm" 1 4)))
    "")
  (equal?
    (with-output-to-string
      (lambda ()
        (close-port $cp-op)))
    "write 13\nclosed\n")
  (error? ; closed
    (put-char $cp-op #\$))
  (error? ; closed
    (put-string $cp-op "3"))
  (error? ; closed
    (r6rs:flush-output-port $cp-op))

  (begin
    (define $cp-iop
      (let ([pos 0] [chars "0123456789abcdefghijklmnopqrstuvwxyz"])
        (make-custom-textual-input/output-port "foo"
          (lambda (str s c)
            (let loop ([i s])
              (unless (eq? i (+ s c))
                (string-set! str i (string-ref chars (modulo (+ pos i) 36)))
                (loop (+ 1 i))))
            (set! pos (+ pos c))
            c)
          (lambda (str s c)
            (set! pos (+ pos c))
            (printf "write ~s\n" c)
            c)
          (lambda () pos)
          (lambda (x) (set! pos x))
          (lambda () (printf "closed\n")))))
    #t)
  (eq? (port-position $cp-iop) 0)
  (error? ; cannot unget
    (unget-char $cp-iop #\$))
  (begin (unget-char $cp-iop (eof-object)) #t)
  (port-eof? $cp-iop)
  (eof-object? (lookahead-char $cp-iop))
  (eof-object? (get-char $cp-iop))
  (equal?
    (get-string-n $cp-iop 10)
    "0123456789")
  (eqv? (port-position $cp-iop) 10)
  (eqv? (get-char $cp-iop) #\a)
  (begin (set-port-position! $cp-iop 36000) #t)
  (eqv? (get-char $cp-iop) #\0)
  (custom-port-warning? (port-position $cp-iop))
  (not (port-has-port-length? $cp-iop))
  (not (port-has-set-port-length!? $cp-iop))
  (not (port-has-port-nonblocking?? $cp-iop))
  (not (port-has-set-port-nonblocking!? $cp-iop))
  (error? ; not supported
    (port-length $cp-iop))
  (error? ; not supported
    (set-port-length! $cp-iop 50))
  (error? ; not supported
    (port-nonblocking? $cp-iop))
  (error? ; not supported
    (set-port-nonblocking! $cp-iop #t))
  (error? ; not supported
    (set-port-nonblocking! $cp-iop #f))
  (custom-port-warning? (put-char $cp-iop #\$))
  (begin (set-port-position! $cp-iop 17) #t)
  (eqv? (port-position $cp-iop) 17)
  (equal?
    (with-output-to-string
      (lambda ()
        (put-string $cp-iop "abcd")
        (put-string $cp-iop "defghi" 1)
        (put-string $cp-iop "hijklm" 1 4)))
    "")
  (equal? ; in our current implementation...
    (with-output-to-string
      (lambda ()
        (printf "pos = ~s\n" (port-position $cp-iop))))
    "write 13\npos = 30\n")
  (equal?
    (with-output-to-string
      (lambda ()
        (printf "pos = ~s\n" (port-position $cp-iop))))
    "pos = 30\n")
  (equal?
    (with-output-to-string
      (lambda ()
        (put-string $cp-iop "abcd")
        (put-string $cp-iop "defghi" 1)
        (put-string $cp-iop "hijklm" 1 4)))
    "")
  (equal?
    (with-output-to-string
      (lambda ()
        (close-port $cp-iop)))
    "write 13\nclosed\n")
  (error? ; closed
    (put-char $cp-iop #\$))
  (error? ; closed
    (put-string $cp-iop "3"))
  (error? ; closed
    (r6rs:flush-output-port $cp-iop))

  (begin
    (define $cp-iop
      (let ([pos 0] [chars "0123456789abcdefghijklmnopqrstuvwxyz"])
        (make-custom-textual-input/output-port "foo"
          (lambda (str s c)
            (let loop ([i s])
              (unless (eq? i (+ s c))
                (string-set! str i (string-ref chars (modulo (+ pos i) 36)))
                (loop (+ 1 i))))
            (set! pos (+ pos c))
            c)
          (lambda (str s c)
            (set! pos (+ pos c))
            (printf "write ~s\n" c)
            c)
          #f
          (lambda (x) (set! pos x))
          (lambda () (printf "closed\n")))))
    #t)
  (not (port-has-port-position? $cp-iop))
  (error? ; operation not supported
    (port-position $cp-iop))
  (begin
    (guard (c [(warning? c) (errorf #f "unexpected warning ~s" c)])
      (put-char $cp-iop #\$))
    #t)
  (eqv? (get-char $cp-iop) #\1)
  (custom-port-warning? ; can't determine position for write
    (put-char $cp-iop #\$))
  (begin (set-port-position! $cp-iop 50) #t)
  (begin
    (guard (c [(warning? c) (errorf #f "unexpected warning ~s" c)])
      (put-char $cp-iop #\$))
    #t)
  (eqv? (get-char $cp-iop) #\f)
  (custom-port-warning? ; can't determine position for write
    (put-string $cp-iop "a"))

  (begin
    (define $cp-iop
      (let ([pos 0] [chars "0123456789abcdefghijklmnopqrstuvwxyz"])
        (make-custom-textual-input/output-port "foo"
          (lambda (str s c)
            (let loop ([i s])
              (unless (eq? i (+ s c))
                (string-set! str i (string-ref chars (modulo (+ pos i) 36)))
                (loop (+ 1 i))))
            (set! pos (+ pos c))
            c)
          (lambda (str s c)
            (set! pos (+ pos c))
            (printf "write ~s\n" c)
            c)
          (lambda () pos)
          #f
          (lambda () (printf "closed\n")))))
    #t)
  (not (port-has-set-port-position!? $cp-iop))
  (error? ; operation not supported
    (set-port-position! $cp-iop 3))
  (begin
    (guard (c [(warning? c) (errorf #f "unexpected warning ~s" c)])
      (put-char $cp-iop #\$))
    #t)
  (eqv? (get-char $cp-iop) #\1)
  (custom-port-warning? ; can't set position for write
   ; convoluted because we want warning to return normally so that operation
   ; is completed
    (let ([hit? #f])
      (with-exception-handler
        (lambda (c) (if (warning? c) (set! hit? c) (raise c)))
        (lambda () (put-char $cp-iop #\$)))
      (when hit? (raise hit?))))
  (begin
    (guard (c [(warning? c) (errorf #f "unexpected warning ~s" c)])
      (put-char $cp-iop #\$))
    #t)
  (begin (get-char $cp-iop) #t) ; position undefined, so value undefined
  (custom-port-warning? ; can't set position for write
    (put-string $cp-iop "a"))

  (begin
    (define $cp-iop
      (let ([pos 0] [chars "0123456789abcdefghijklmnopqrstuvwxyz"])
        (make-custom-textual-input/output-port "foo"
          (lambda (str s c)
            (let loop ([i s])
              (unless (eq? i (+ s c))
                (string-set! str i (string-ref chars (modulo (+ pos i) 36)))
                (loop (+ 1 i))))
            (set! pos (+ pos c))
            c)
          (lambda (str s c)
            (set! pos (+ pos c))
            (printf "write ~s\n" c)
            c)
          #f
          #f
          (lambda () (printf "closed\n")))))
    #t)
  (not (port-has-port-position? $cp-iop))
  (error? ; operation not supported
    (port-position $cp-iop))
  (not (port-has-set-port-position!? $cp-iop))
  (error? ; operation not supported
    (set-port-position! $cp-iop 3))
  (begin
    (guard (c [(warning? c) (errorf #f "unexpected warning ~s" c)])
      (put-char $cp-iop #\$))
    #t)
  (eqv? (get-char $cp-iop) #\1)
  (custom-port-warning? ; can't determine position for write
   ; convoluted because we want warning to return normally so that operation
   ; is completed
    (let ([hit? #f])
      (with-exception-handler
        (lambda (c) (if (warning? c) (set! hit? c) (raise c)))
        (lambda () (put-char $cp-iop #\$)))
      (when hit? (raise hit?))))
  (begin
    (guard (c [(warning? c) (errorf #f "unexpected warning ~s" c)])
      (put-char $cp-iop #\$))
    #t)
  (begin (get-char $cp-iop) #t) ; position undefined, so value undefined
  (custom-port-warning? ; can't determine position for write
    (put-string $cp-iop "a"))

  (equal?
    (let-values ([(sop get) (open-string-output-port)])
      (define op
        (make-custom-textual-output-port "foo"
          (lambda (str s c)
            (put-string sop str s c)
            c)
          #f #f #f))
      (fresh-line op)
      (fresh-line op)
      (put-string op "hello")
      (fresh-line op)
      (fresh-line op)
      (put-string op "hello")
      (flush-output-port op)
      (fresh-line op)
      (fresh-line op)
      (put-string op "hello\n")
      (flush-output-port op)
      (fresh-line op)
      (fresh-line op)
      (put-string op "hello\n")
      (fresh-line op)
      (close-port op)
      (get))
    "hello\nhello\nhello\nhello\n")

  (equal?
    (let-values ([(sop get) (open-string-output-port)])
      (define op
        (make-custom-textual-input/output-port "foo"
          (lambda (str s c) (errorf #f "oops"))
          (lambda (str s c)
            (put-string sop str s c)
            c)
          #f #f #f))
      (fresh-line op)
      (fresh-line op)
      (put-string op "hello")
      (fresh-line op)
      (fresh-line op)
      (put-string op "hello")
      (flush-output-port op)
      (fresh-line op)
      (fresh-line op)
      (put-string op "hello\n")
      (flush-output-port op)
      (fresh-line op)
      (fresh-line op)
      (put-string op "hello\n")
      (fresh-line op)
      (close-port op)
      (get))
    "hello\nhello\nhello\nhello\n")
)

(mat compression-textual
  (parameters [compress-format 'gzip 'lz4] [compress-level 'low 'medium 'high 'maximum])
  (let ()
    (define cp
      (lambda (src dst)
        (define buf-size 103)
        (let ([buf (make-string buf-size)])
          (call-with-port dst
            (lambda (op)
              (call-with-port src
                (lambda (ip)
                  (let loop ()
                    (do ([i 0 (fx+ i 1)])
                        ((fx= i buf-size))
                      (let ([c (get-char ip)])
                        (unless (eof-object? c) (put-char op c))))
                    (let ([n (get-string-n! ip buf 0 buf-size)])
                      (unless (eof-object? n)
                        (put-string op buf 0 n)
                        (loop)))))))))))
    (define cmp
      (lambda (src1 src2)
        (define buf-size 128)
        (let ([buf (make-string buf-size)])
          (call-with-port src1
            (lambda (ip1)
              (call-with-port src2
                (lambda (ip2)
                  (let loop ([pos 0])
                    (let ([n (get-string-n! ip1 buf 0 buf-size)])
                      (if (eof-object? n)
                          (unless (eof-object? (lookahead-char ip2)) (errorf #f "ip1 eof before ip2"))
                          (if (eof-object? (lookahead-char ip2))
                              (errorf #f "ip2 eof before ip1")
                              (let test ([i 0] [pos pos])
                                (if (= i n)
                                    (loop pos)
                                    (let ([c1 (string-ref buf i)] [c2 (get-char ip2)])
                                      (if (char=? c1 c2)
                                          (test (+ 1 i) (+ pos 1))
                                          (errorf #f "ip1 c = ~s =/= ip2 c = ~s at pos ~s" c1 c2 pos))))))))))))))))
    (define (in fn compressed? codec)
      (open-file-input-port fn
        (if compressed? (file-options compressed) (file-options))
        (buffer-mode block)
        (make-transcoder codec)))
    (define (out fn compressed? codec)
      (open-file-output-port fn
        (if compressed? (file-options compressed replace) (file-options replace))
        (buffer-mode block)
        (make-transcoder codec)))
    (time (cmp (in prettytest.ss #f (latin-1-codec)) (in prettytest.ss #f (latin-1-codec))))
    (time (cmp (in prettytest.ss #t (latin-1-codec)) (in prettytest.ss #f (latin-1-codec))))
    (time (cmp (in prettytest.ss #f (latin-1-codec)) (in prettytest.ss #t (latin-1-codec))))
    (time (cmp (in prettytest.ss #t (latin-1-codec)) (in prettytest.ss #t (latin-1-codec))))
    (time (cmp (in prettytest.ss #f (utf-8-codec)) (in prettytest.ss #f (utf-8-codec))))
    (time (cmp (in prettytest.ss #t (utf-8-codec)) (in prettytest.ss #f (utf-8-codec))))
    (time (cmp (in prettytest.ss #f (utf-8-codec)) (in prettytest.ss #t (utf-8-codec))))
    (time (cmp (in prettytest.ss #t (utf-8-codec)) (in prettytest.ss #t (utf-8-codec))))
    (cp (in prettytest.ss #f (latin-1-codec)) (out "testfile.ss" #t (latin-1-codec)))
    (cmp (in prettytest.ss #f (latin-1-codec)) (in "testfile.ss" #t (latin-1-codec)))
    (cmp (in prettytest.ss #t (latin-1-codec)) (in "testfile.ss" #t (latin-1-codec)))
    (cmp (in prettytest.ss #f (utf-8-codec)) (in "testfile.ss" #t (utf-8-codec)))
    (cmp (in prettytest.ss #t (utf-8-codec)) (in "testfile.ss" #t (utf-8-codec)))
    (cp (in prettytest.ss #f (utf-8-codec)) (out "testfile.ss" #t (utf-8-codec)))
    (cmp (in prettytest.ss #f (latin-1-codec)) (in "testfile.ss" #t (latin-1-codec)))
    (cmp (in prettytest.ss #t (latin-1-codec)) (in "testfile.ss" #t (latin-1-codec)))
    (cmp (in prettytest.ss #f (utf-8-codec)) (in "testfile.ss" #t (utf-8-codec)))
    (cmp (in prettytest.ss #t (utf-8-codec)) (in "testfile.ss" #t (utf-8-codec)))
    #t)
  ; test workaround for bogus gzclose error return for empty input files
  (and
    (eqv? (call-with-port
            (open-file-output-port "testfile.ss" (file-options replace) (buffer-mode block) (native-transcoder))
            (lambda (x) (void)))
          (void))
    (eof-object?
      (call-with-port
        (open-file-input-port "testfile.ss" (file-options compressed)
          (buffer-mode block) (native-transcoder))
        get-char)))
)

(mat string-ports
  (let ()
    (define pretty-test-string
      (call-with-port
        (open-file-input-port prettytest.ss
          (file-options) (buffer-mode none) (native-transcoder))
        get-string-all))
    (define cp ; doesn't close the ports
      (lambda (ip op)
        (define buf-size 103)
        (let ([buf (make-string buf-size)])
          (let loop ()
            (do ([i 0 (fx+ i 1)])
                ((fx= i buf-size))
              (let ([c (get-char ip)])
                (unless (eof-object? c) (put-char op c))))
            (let ([n (get-string-n! ip buf 0 buf-size)])
              (unless (eof-object? n)
                (put-string op buf 0 n)
                (loop)))))))
    (define cmp
      (lambda (src1 src2)
        (define buf-size 64)
        (let ([buf (make-string buf-size)])
          (call-with-port src1
            (lambda (ip1)
              (call-with-port src2
                (lambda (ip2)
                  (let loop ([pos 0])
                    (let ([n (get-string-n! ip1 buf 0 buf-size)])
                      (if (eof-object? n)
                          (unless (eof-object? (lookahead-char ip2)) (errorf #f "ip1 eof before ip2"))
                          (if (eof-object? (lookahead-char ip2))
                              (errorf #f "ip2 eof before ip1")
                              (let test ([i 0] [pos pos])
                                (if (= i n)
                                    (loop pos)
                                    (let ([c1 (string-ref buf i)] [c2 (get-char ip2)])
                                      (if (char=? c1 c2)
                                          (test (+ 1 i) (+ pos 1))
                                          (errorf #f "ip1 c = ~s =/= ip2 c = ~s at pos ~s" c1 c2 pos))))))))))))))))
    (define (in fn compressed? codec)
      (open-file-input-port fn
        (if compressed? (file-options compressed) (file-options))
        (buffer-mode block)
        (make-transcoder codec)))
    (define (out fn compressed? codec)
      (open-file-output-port fn
        (if compressed? (file-options compressed replace) (file-options replace))
        (buffer-mode block)
        (make-transcoder codec)))
    (time (cmp (in prettytest.ss #f (latin-1-codec)) (open-string-input-port pretty-test-string)))
    (time (cmp (open-string-input-port pretty-test-string) (in prettytest.ss #f (latin-1-codec))))
    (let-values ([(op retrieve) (open-string-output-port)])
      (cp (open-string-input-port pretty-test-string) op)
      (cmp (in prettytest.ss #f (latin-1-codec)) (open-string-input-port (retrieve))))
    #t)
)

(mat current-ports
  (input-port? (current-input-port))
  (textual-port? (current-input-port))
  (not (output-port? (open-input-string "hello")))
  (output-port? (current-output-port))
  (textual-port? (current-output-port))
  (output-port? (current-error-port))
  (textual-port? (current-error-port))
  (not (input-port? (open-output-string)))
  (eq? (r6rs:current-input-port) (current-input-port))
  (eq? (r6rs:current-output-port) (current-output-port))
  (eq? (r6rs:current-error-port) (current-error-port))
  (equal?
    (with-output-to-string
      (lambda ()
        (write (list
                 (eq? (r6rs:current-input-port) (current-input-port))
                 (eq? (r6rs:current-output-port) (current-output-port))
                 (eq? (r6rs:current-error-port) (current-error-port))))))
    "(#t #t #t)")
  (error? (current-input-port (standard-input-port)))
  (error? (current-output-port (standard-output-port)))
  (error? (current-error-port (standard-output-port)))
  (error? (current-input-port (open-output-string)))
  (error? (current-output-port (open-input-string "")))
  (error? (current-error-port (open-input-string "")))
  (error? (console-input-port (standard-input-port)))
  (error? (console-output-port (standard-output-port)))
  (error? (console-error-port (standard-output-port)))
  (error? (console-input-port (open-output-string)))
  (error? (console-output-port (open-input-string "")))
  (error? (console-error-port (open-input-string "")))
)

(mat current-transcoder
  (transcoder? (current-transcoder))
  (eqv? (current-transcoder) (native-transcoder))
  (error? (current-transcoder (open-output-string)))
  (parameterize ([current-transcoder (native-transcoder)])
    (eqv? (current-transcoder) (native-transcoder)))
  (parameterize ([current-transcoder (make-transcoder (utf-16le-codec))])
    (with-output-to-file "testfile.ss" (lambda () (write '\x3bb;12345)) 'replace)
    (file-exists? "testfile.ss"))
  (parameterize ([current-transcoder (make-transcoder (utf-16le-codec))])
    (with-input-from-file "testfile.ss"
      (lambda ()
        (and (eqv? (read) '\x3bb;12345) (eof-object? (read))))))
  (equal?
    (call-with-port (open-file-input-port "testfile.ss") get-bytevector-all)
    #vu8(#xBB #x3 #x31 #x0 #x32 #x0 #x33 #x0 #x34 #x0 #x35 #x0))
)

(mat get/put-datum
  (error? (get-datum))
  (error? (get-datum (current-input-port) (current-input-port)))
  (error? (get-datum (open-output-string)))
  (error? (get-datum (open-bytevector-input-port #vu8())))
  (call-with-port
    (open-string-input-port "hey #;there dude!")
    (lambda (p)
      (and (eq? (get-datum p) 'hey)
           (eqv? (get-char p) #\space)
           (eq? (get-datum p) 'dude!)
           (eof-object? (get-datum p)))))
  (error? (put-datum))
  (error? (put-datum (current-output-port)))
  (error? (put-datum (current-output-port) 'a 'a))
  (error? (put-datum (open-input-string "hello") 'a))
  (error? (put-datum (let-values ([(p g) (open-bytevector-output-port)]) p) 'a))
  (equal?
    (let-values ([(p g) (open-string-output-port)])
      (put-datum p '(this is))
      (put-datum p "cool")
      (put-datum p '(or (maybe . not)))
      (g))
    "(this is)\"cool\"(or (maybe . not))")
  (call-with-port
    (open-string-input-port "#3(a b c) #!r6rs #(d e) #!chezscheme #3(f g)")
    (lambda (p)
      (and
        (equal? (get-datum p) '#(a b c))
        (equal? (get-datum p) '#(d e))
        (equal? (get-datum p) '#(f g g))
        (equal? (get-datum p) #!eof))))
 ; make sure that nel and ls are treated properly
  (call-with-port
    (open-string-input-port "#!r6rs \x85; \x2028; #\\x85 #\\x2028 \"\\x85; \\x2028;\"")
    (lambda (p)
      (and
        (equal? (get-datum p) (integer->char #x85))
        (equal? (get-datum p) (integer->char #x2028))
        (equal? (get-datum p) (string (integer->char #x85) #\space (integer->char #x2028))))))
  (equal?
    (call-with-string-output-port
      (lambda (p)
        (put-char p #\x85)
        (put-char p #\space)
        (put-char p #\x2028)
        (put-char p #\space)
        (put-datum p #\x85)
        (put-char p #\space)
        (put-datum p #\x2028)
        (put-char p #\space)
        (put-datum p "\x85; \x2028;")))
    "\x85; \x2028; #\\x85 #\\x2028 \"\\x85; \\x2028;\"")
  (let ()
    (define (rw? x1)
      (let ([str (let-values ([(p e) (open-string-output-port)])
                   (write x1 p)
                   (e))])
        (let ([x2 (read (open-string-input-port str))])
          (equal? x1 x2))))
    (and
      (rw? "  \x85;  ")
      (rw? "  \x2028;  ")
      (rw? #\x85)
      (rw? #\x2028)))
)

(mat utf-16-codec
  (error? (r6rs:utf-16-codec #f))
  (error? (utf-16-codec #f))
 ; test decoding
  (let ()
    (define utf-16->string
      (lambda (eol bv)
        (let ([ip (transcoded-port
                    (let ([n (bytevector-length bv)] [i 0])
                      (make-custom-binary-input-port "foo"
                        (lambda (buf start count)
                          (let ([count (min (+ (random (min count 3)) 1) (fx- n i))])
                            (bytevector-copy! bv i buf start count)
                            (set! i (+ i count))
                            count))
                        (lambda () i)
                        (lambda (p) (set! i p))
                        #f))
                    (make-transcoder (utf-16-codec) eol (error-handling-mode replace)))])
          (call-with-string-output-port
            (lambda (op)
              (define (deref s) (if (eof-object? s) s (string-ref s 0)))
              (let again ()
                (let ([c (if (= (random 5) 3) (deref (get-string-n ip 1)) (get-char ip))])
                  (if (eof-object? c)
                      (let ([pos (port-position ip)])
                        (unless (= pos (bytevector-length bv))
                          (errorf #f "wrong pos ~s at eof" pos)))
                      (begin (put-char op c) (again))))))))))
    (define (big bv)
      (let ([n (bytevector-length bv)])
        (let ([newbv (make-bytevector (+ n 2))])
          (bytevector-u8-set! newbv 0 #xfe)
          (bytevector-u8-set! newbv 1 #xff)
          (do ([i 0 (fx+ i 2)])
              ((fx>= i (fx- n 1))
               (unless (fx= i n)
                 (bytevector-u8-set! newbv (fx+ i 2) (bytevector-u8-ref bv i))))
            (bytevector-u8-set! newbv (fx+ i 2) (bytevector-u8-ref bv i))
            (bytevector-u8-set! newbv (fx+ i 3) (bytevector-u8-ref bv (fx+ i 1))))
          newbv)))
    (define (little bv)
      (let ([n (bytevector-length bv)])
        (let ([newbv (make-bytevector (+ n 2))])
          (bytevector-u8-set! newbv 0 #xff)
          (bytevector-u8-set! newbv 1 #xfe)
          (do ([i 0 (fx+ i 2)])
              ((fx>= i (fx- n 1))
               (unless (fx= i n)
                 (bytevector-u8-set! newbv (fx+ i 2) (bytevector-u8-ref bv i))))
            (bytevector-u8-set! newbv (fx+ i 2) (bytevector-u8-ref bv (fx+ i 1)))
            (bytevector-u8-set! newbv (fx+ i 3) (bytevector-u8-ref bv i)))
          newbv)))
    (define (test eol bv s)
      (do ([n 1000 (fx- n 1)])
          ((fx= n 0))
        (let ([seed (random-seed)])
          (unless (and (equal? (utf-16->string eol bv) s)
                       (equal? (utf-16->string eol (big bv)) s)
                       (equal? (utf-16->string eol (little bv)) s))
            (errorf #f "failed, seed = ~s, bv = ~s, s = ~s" seed bv s)))))
    (test 'lf #vu8(#x00 #x61 #x00 #x0a) "a\n")
    (test 'lf #vu8(#x00 #x61 #x00 #x0d #x00 #x0a #x00 #x0d #x00 #x85 #x00 #x0d #x00 #x0d #x00 #x0a #x00 #x0d #x20 #x28) "a\n\n\n\n\n\n")
    (test 'crlf #vu8(#x00 #x61 #x00 #x0d #x00 #x0a #x00 #x0d #x00 #x85 #x00 #x0d #x00 #x0d #x00 #x0a #x00 #x0d #x20 #x28) "a\n\n\n\n\n\n")
    (test 'none #vu8(#x00 #x61 #x00 #x0d #x00 #x0a #x00 #x0d #x00 #x85 #x00 #x0d #x00 #x0d #x00 #x0a #x00 #x0d #x20 #x28) "a\r\n\r\x85;\r\r\n\r\x2028;")
    (test 'lf #vu8(#x00 #x0a #xd8 #x00 #xdc #x00 #xdb #xff #xdf #xff #xdc #x00 #xd8 #x00 #x00 #x00 #x00) "\n\x10000;\x10ffff;\xfffd;\xfffd;\xfffd;")
    #t)
 ; test encoding
  (let ()
    (define string->utf-16
      (lambda (eol s)
        (let-values ([(op getbv)
                      (let-values ([(bvop getbv) (open-bytevector-output-port)])
                        (values
                          (transcoded-port
                            (let ([i 0])
                              (make-custom-binary-output-port "foo"
                                (lambda (buf start count)
                                  (let ([count (random (min (fx+ count 1) 4))])
                                    (put-bytevector bvop buf start count)
                                    (set! i (+ i count))
                                    count))
                                (lambda () i)
                                #f #f))
                            (make-transcoder (utf-16be-codec) eol (error-handling-mode replace)))
                          getbv))])
          (let ([sip (open-string-input-port s)])
            (define (deref s) (if (eof-object? s) s (string-ref s 0)))
            (let again ()
              (let ([c (get-char sip)])
                (if (eof-object? c)
                    (let ([pos (port-position op)])
                      (close-port op)
                      (let ([bv (getbv)])
                        (unless (= pos (bytevector-length bv))
                          (errorf #f "wrong pos ~s at eof" pos))
                        bv))
                    (begin
                      (if (= (random 5) 3)
                          (put-string op (string c))
                          (put-char op c))
                      (again)))))))))
    (define (test eol s bv)
      (do ([n 1000 (fx- n 1)])
          ((fx= n 0))
        (let ([seed (random-seed)])
          (unless (equal? (string->utf-16 eol s) bv)
            (errorf #f "failed, seed = ~s, s = ~s, bv = ~s" seed s bv)))))
    (test 'lf "a\n" #vu8(#x00 #x61 #x00 #x0a))
    (test 'crlf "a\n" #vu8(#x00 #x61 #x00 #x0d #x00 #x0a))
    (test 'crnel "a\n" #vu8(#x00 #x61 #x00 #x0d #x00 #x85))
    (test 'nel "a\n" #vu8(#x00 #x61 #x00 #x85))
    (test 'ls "a\n" #vu8(#x00 #x61 #x20 #x28))
    (test 'none "a\r\n\r\x85;\r\r\n\r\x2028;" #vu8(#x00 #x61 #x00 #x0d #x00 #x0a #x00 #x0d #x00 #x85 #x00 #x0d #x00 #x0d #x00 #x0a #x00 #x0d #x20 #x28))
    (test 'lf "a\x10000;\x10ffff;\n" #vu8(#x00 #x61 #xd8 #x00 #xdc #x00 #xdb #xff #xdf #xff #x00 #x0a))
    #t)
)

(mat utf-16-BOMs
  (let ()
    (define utf-16-tx (make-transcoder (utf-16-codec) (eol-style none) (error-handling-mode raise)))
    (and
      (let ()
        (define iop
          (open-file-input/output-port "testfile.ss" (file-options replace)
            (buffer-mode block) utf-16-tx))
        (define n (port-position iop))            ; should be 0
        (put-string iop "hello\n")                ; should write BOM
        (set-port-position! iop n)                ; should actually position past BOM (position 2)
        (and
          (eqv? n 0)
          (eqv? (port-position iop) 2)
          (equal? (get-string-all iop) "hello\n")
          (eq? (close-port iop) (void))))
      (let ()
        (define iop
          (open-file-input/output-port "testfile.ss" (file-options no-fail no-truncate)
            (buffer-mode block) utf-16-tx))
        (define n (port-position iop))
        (and
          (eqv? n 0)
          (eqv? (get-char iop) #\h)
          (eqv? (port-position iop) 4)
          (equal? (get-string-all iop) "ello\n")
          (eqv? (port-position iop) 14)
          (eq? (set-port-position! iop n) (void))
          (eqv? (port-position iop) 2)
          (put-string iop "something longer than hello\n")
          (eq? (set-port-position! iop n) (void))
          (equal? (get-string-all iop) "something longer than hello\n")
          (eq? (close-port iop) (void))))))
  (let () ; same as preceding w/slightly different transcoder
    (define utf-16-tx (make-transcoder (utf-16-codec) (eol-style lf) (error-handling-mode replace)))
    (and
      (let ()
        (define iop
          (open-file-input/output-port "testfile.ss" (file-options replace)
            (buffer-mode block) utf-16-tx))
        (define n (port-position iop))            ; should be 0
        (put-string iop "hello\n")                ; should write BOM
        (set-port-position! iop n)                ; should actually position past BOM (position 2)
        (and
          (eqv? n 0)
          (eqv? (port-position iop) 2)
          (equal? (get-string-all iop) "hello\n")
          (eq? (close-port iop) (void))))
      (let ()
        (define iop
          (open-file-input/output-port "testfile.ss" (file-options no-fail no-truncate)
            (buffer-mode block) utf-16-tx))
        (define n (port-position iop))
        (and
          (eqv? n 0)
          (equal? (get-string-all iop) "hello\n")
          (eq? (set-port-position! iop n) (void))
          (eqv? (port-position iop) 2)
          (put-string iop "something longer than hello\n")
          (eq? (set-port-position! iop n) (void))
          (equal? (get-string-all iop) "something longer than hello\n")
          (eq? (close-port iop) (void))))))
  (let ()
    (define utf-16-tx (make-transcoder (utf-16-codec) (eol-style none) (error-handling-mode raise)))
    (define utf-16be-tx (make-transcoder (utf-16be-codec) (eol-style none) (error-handling-mode raise)))
    (and
      (let ()
        (define iop
          (open-file-input/output-port "testfile.ss" (file-options replace)
            (buffer-mode block) utf-16-tx))
        (define n (port-position iop))            ; should be 0
        (put-string iop "hello\n")                ; should write BOM
        (set-port-position! iop n)                ; should actually position past BOM (position 2)
        (and
          (eqv? n 0)
          (eqv? (port-position iop) 2)
          (equal? (get-string-all iop) "hello\n")
          (eq? (close-port iop) (void))))
      (let ()
        (define iop
          (open-file-input/output-port "testfile.ss" (file-options no-fail no-truncate)
            (buffer-mode block) utf-16-tx))
       ; lookahead-char should position port past the BOM
        (define c (lookahead-char iop))
        (define n (port-position iop))            ; should be 2
        (and
          (eqv? c #\h)
          (eqv? n 2)
          (equal? (get-string-all iop) "hello\n")
          (eq? (set-port-position! iop n) (void))
          (eq? (put-string iop "something longer than hello\n") (void))
          (eq? (set-port-position! iop n) (void))
          (equal? (get-string-all iop) "something longer than hello\n")
          (eq? (close-port iop) (void))))
      (let ()
        (define iop
          (open-file-input/output-port "testfile.ss" (file-options no-fail no-truncate)
            (buffer-mode block) utf-16be-tx))
        (define n (port-position iop))            ; should be 0
        (and
          (eqv? (get-char iop) #\xfeff)
          (equal? (get-string-all iop) "something longer than hello\n")
          (eq? (set-port-position! iop n) (void))
          (eqv? (get-char iop) #\xfeff)
          (equal? (get-string-all iop) "something longer than hello\n")
          (eq? (close-port iop) (void))))))
  (let ()
    (define utf-16le-tx (make-transcoder (utf-16le-codec) (eol-style none) (error-handling-mode raise)))
    (and
      (let ()
        (define iop
          (open-file-input/output-port "testfile.ss" (file-options replace)
            (buffer-mode block) utf-16le-tx))
        (define n (port-position iop))            ; should be 0
        (put-string iop "hello\n")                ; should not write BOM
        (set-port-position! iop n)                ; should set to 0
        (and
          (eqv? n 0)
          (eqv? (port-position iop) 0)
          (equal? (get-string-all iop) "hello\n")
          (eq? (close-port iop) (void))))
      (let ()
        (define iop
          (open-file-input/output-port "testfile.ss" (file-options no-fail no-truncate)
            (buffer-mode block) utf-16le-tx))
        (define n (port-position iop))            ; should be 0
        (and
          (eq? n 0)
          (equal? (get-string-all iop) "hello\n")
          (eq? (set-port-position! iop n) (void))
          (eqv? (port-position iop) 0)
          (eq? (put-string iop "something longer than hello\n") (void))
          (eq? (set-port-position! iop n) (void))
          (eqv? (port-position iop) 0)
          (equal? (get-string-all iop) "something longer than hello\n")
          (eq? (close-port iop) (void))))))
  (let ()
    (define utf-16be-tx (make-transcoder (utf-16be-codec) (eol-style none) (error-handling-mode raise)))
    (and
      (let ()
        (define iop
          (open-file-input/output-port "testfile.ss" (file-options replace)
            (buffer-mode block) utf-16be-tx))
        (define n (port-position iop))            ; should be 0
        (put-string iop "hello\n")                ; should not write BOM
        (set-port-position! iop n)                ; should set to 0
        (and
          (eqv? n 0)
          (eqv? (port-position iop) 0)
          (equal? (get-string-all iop) "hello\n")
          (eq? (close-port iop) (void))))
      (let ()
        (define iop
          (open-file-input/output-port "testfile.ss" (file-options no-fail no-truncate)
            (buffer-mode block) utf-16be-tx))
        (define n (port-position iop))            ; should be 0
        (and
          (eq? n 0)
          (equal? (get-string-all iop) "hello\n")
          (eq? (set-port-position! iop n) (void))
          (eqv? (port-position iop) 0)
          (eq? (put-string iop "something longer than hello\n") (void))
          (eq? (set-port-position! iop n) (void))
          (eqv? (port-position iop) 0)
          (equal? (get-string-all iop) "something longer than hello\n")
          (eq? (close-port iop) (void))))))
  (let ()
    (define utf-16be-tx (make-transcoder (utf-16be-codec) (eol-style none) (error-handling-mode raise)))
    (define utf-16-tx (make-transcoder (utf-16-codec) (eol-style none) (error-handling-mode raise)))
    (and
      (let ()
        (define iop
          (open-file-input/output-port "testfile.ss" (file-options replace)
            (buffer-mode block) utf-16be-tx))
        (define n (port-position iop))            ; should be 0
        (put-string iop "hello\n")                ; should not write BOM
        (set-port-position! iop n)                ; should set to 0
        (and
          (eqv? n 0)
          (eqv? (port-position iop) 0)
          (equal? (get-string-all iop) "hello\n")
          (eq? (close-port iop) (void))))
      (let ()
        (define iop
          (open-file-input/output-port "testfile.ss" (file-options no-fail no-truncate)
            (buffer-mode block) utf-16-tx))
        (define n (port-position iop))            ; should be 0
        (and
          (eq? n 0)
          (equal? (get-string-all iop) "hello\n")
          (eq? (set-port-position! iop n) (void))
          (eqv? (port-position iop) 0)
          (eq? (put-string iop "something longer than hello\n") (void))
          (eq? (set-port-position! iop n) (void))
          (eqv? (port-position iop) 0)
          (equal? (get-string-all iop) "something longer than hello\n")
          (eq? (close-port iop) (void))))))
  (let ()
    (define utf-16le-tx (make-transcoder (utf-16le-codec) (eol-style none) (error-handling-mode raise)))
    (define utf-16-tx (make-transcoder (utf-16-codec) (eol-style none) (error-handling-mode raise)))
    (and
      (let ()
        (define iop
          (open-file-input/output-port "testfile.ss" (file-options replace)
            (buffer-mode block) utf-16le-tx))
        (define n0 (port-position iop))             ; should be 0
        (put-char iop #\xfeff)                      ; insert explicit BOM
        (let ()
          (define n (port-position iop))            ; should be 0
          (put-string iop "hello\n")                ; should not write BOM
          (set-port-position! iop n)                ; should set to 0
          (and
            (eqv? n0 0)
            (eqv? n 2)
            (equal? (get-string-all iop) "hello\n")
            (eq? (close-port iop) (void)))))
      (let ()
        (define iop
          (open-file-input/output-port "testfile.ss" (file-options no-fail no-truncate)
            (buffer-mode block) utf-16-tx))
        (define n (port-position iop))
        (and (equal? (get-string-all iop) "hello\n")
             (begin
               (set-port-position! iop n)
               (put-string iop "hello again\n")
               (set-port-position! iop n))
             (and (equal? (get-string-all iop) "hello again\n")
                  (eq? (close-port iop) (void)))))
      (let ()
        (define iop
          (open-file-input/output-port "testfile.ss" (file-options no-fail no-truncate)
            (buffer-mode block) utf-16le-tx))
        (define n (port-position iop))            ; should be 0
        (and
          (eqv? (get-char iop) #\xfeff)           ; BOM should still be there
          (equal? (get-string-all iop) "hello again\n")
          (eq? (set-port-position! iop n) (void))
          (eqv? (port-position iop) 0)
          (eq? (put-string iop "hello yet again!\n") (void))
          (eq? (set-port-position! iop n) (void))
          (eqv? (port-position iop) 0)
          (equal? (get-string-all iop) "hello yet again!\n")   ; BOM is gone now
          (eq? (close-port iop) (void))))))
  (let ()
    (define utf-16le-tx (make-transcoder (utf-16le-codec) (eol-style none) (error-handling-mode raise)))
    (define faux-utf-16-tx (make-transcoder (utf-16-codec 'little) (eol-style none) (error-handling-mode raise)))
    (and
      (let ()
        (define iop
          (open-file-input/output-port "testfile.ss" (file-options replace)
            (buffer-mode block) utf-16le-tx))
        (define n (port-position iop))            ; should be 0
        (put-string iop "hello\n")
        (set-port-position! iop n)
        (and
          (eqv? n 0)
          (eqv? (port-position iop) 0)
          (equal? (get-string-all iop) "hello\n")
          (eq? (close-port iop) (void))))
      (let ()
        (define iop
          (open-file-input/output-port "testfile.ss" (file-options no-fail no-truncate)
            (buffer-mode block) faux-utf-16-tx))
        (define n (port-position iop))            ; should be 0
        (and
          (eqv? n 0)
          (equal? (get-string-all iop) "hello\n")
          (eq? (set-port-position! iop n) (void))
          (eqv? (port-position iop) 0)
          (eq? (put-string iop "hello again\n") (void))
          (eq? (set-port-position! iop n) (void))
          (eqv? (port-position iop) 0)
          (equal? (get-string-all iop) "hello again\n")
          (eq? (close-port iop) (void))))
      (let ()
        (define iop
          (open-file-input/output-port "testfile.ss" (file-options no-fail no-truncate)
            (buffer-mode block) utf-16le-tx))
        (define n (port-position iop))            ; should be 0
        (and
          (eqv? n 0)
          (equal? (get-string-all iop) "hello again\n")
          (eq? (set-port-position! iop n) (void))
          (eqv? (port-position iop) 0)
          (eq? (put-string iop "hello yet again!\n") (void))
          (eq? (set-port-position! iop n) (void))
          (eqv? (port-position iop) 0)
          (equal? (get-string-all iop) "hello yet again!\n")
          (eq? (close-port iop) (void))))))
  (let ()
    (define-syntax and
      (let ()
        (import scheme)
        (syntax-rules ()
          [(_ e ...)
           (and (let ([x e]) (pretty-print x) x) ...)])))
    (define utf-16-tx (make-transcoder (utf-16-codec) (eol-style none) (error-handling-mode raise)))
    (and
      (let ()
        (define op
          (open-file-output-port "testfile.ss" (file-options replace)
            (buffer-mode block) utf-16-tx))
        (define n (port-position op))                  ; should be 0
        (and
          (eqv? n 0)
          (eq? (put-string op "hello\n") (void))       ; should write BOM
          (eq? (set-port-position! op n) (void))       ; should actually position past BOM (position 2)
          (eqv? (port-position op) 2)
          (eq? (put-string op "not hello\n") (void))   ; should not write (another) BOM
          (eq? (close-port op) (void))))
      (let ()
        (define ip
          (open-file-input-port "testfile.ss" (file-options)
            (buffer-mode block) utf-16-tx))
        (define n (port-position ip))                  ; should be 0
        (define c (lookahead-char ip))                 ; should be #\n
        (and
          (eqv? n 0)
          (eqv? c #\n)
          (eqv? (port-position ip) 2)
          (equal? (get-string-all ip) "not hello\n")
          (eq? (set-port-position! ip 2) (void))
          (equal? (get-string-all ip) "not hello\n")
          (eq? (close-port ip) (void))))))
)

(mat encode/decode-consistency
 ; verify that encoding/decoding is consistent (but not necessarily correct)
 ; crank up loop bounds to stress test
  (let ()
    (define (random-string n)
      (define (random-char) (integer->char (random 256)))
      (let ([s (make-string n)])
        (do ([i 0 (fx+ i 1)])
            ((fx= i n))
          (string-set! s i (random-char)))
        s))
    (define (check who s1 s2)
      (unless (string=? s1 s2)
        (errorf who "failed for ~a"
          (parameterize ([print-unicode #f]) (format "~s" s1)))))
    (time
      (let ([latin-1-tx (make-transcoder (latin-1-codec) (eol-style none) (error-handling-mode raise))])
        (do ([n 1000 (fx- n 1)])
            ((fx= n 0) #t)
          (let ([s (random-string (random 50))])
            (check 'latin-1-test4 s (bytevector->string (string->bytevector s latin-1-tx) latin-1-tx)))))))
  (let ()
    (define (random-string n)
      (define (random-char)
        (integer->char
          (let ([k (random (fx- #x110000 (fx- #xe000 #xd800)))])
            (if (fx>= k #xd800)
                (fx+ k (fx- #xe000 #xd800))
                k))))
      (let ([s (make-string n)])
        (unless (fx= n 0)
         ; don't let a BOM sneak in at first character
          (string-set! s 0
            (let f () (let ([c (random-char)]) (if (memv c '(#\xfeff #\xfffe)) (f) c))))
          (do ([i 1 (fx+ i 1)])
              ((fx= i n))
            (string-set! s i (random-char))))
        s))
    (define (check who s1 s2)
      (unless (string=? s1 s2)
        (errorf who "failed for ~a"
          (parameterize ([print-unicode #f]) (format "~s" s1)))))
    (time
      (let ()
        (define utf-8-tx (make-transcoder (utf-8-codec) (eol-style none) (error-handling-mode raise)))
        (define utf-16-tx (make-transcoder (utf-16-codec) (eol-style none) (error-handling-mode raise)))
        (define utf-16le-tx (make-transcoder (utf-16le-codec) (eol-style none) (error-handling-mode raise)))
        (define utf-16be-tx (make-transcoder (utf-16be-codec) (eol-style none) (error-handling-mode raise)))
        (do ([n 1000 (fx- n 1)])
            ((fx= n 0) #t)
          (let ([s (random-string (random 50))])
            (check 'utf-8-test1 s (utf8->string (string->utf8 s)))
            (check 'utf-8-test2 s (utf8->string (string->bytevector s utf-8-tx)))
            (check 'utf-8-test3 s (bytevector->string (string->utf8 s) utf-8-tx))
            (check 'utf-8-test4 s (bytevector->string (string->bytevector s utf-8-tx) utf-8-tx))
            (check 'utf-16-test1a s (utf16->string (string->utf16 s 'big) 'big))
            (check 'utf-16-test1b s (utf16->string (string->utf16 s 'big) 'big #t))
            (check 'utf-16-test2a s (utf16->string (string->bytevector s utf-16-tx) 'big))
            (check 'utf-16-test2b s (utf16->string (string->bytevector s utf-16be-tx) 'big #t))
            (check 'utf-16-test2c s (utf16->string (string->bytevector s utf-16le-tx) 'little #t))
            (check 'utf-16-test3a s (bytevector->string (string->utf16 s 'big) utf-16-tx))
            (check 'utf-16-test3b s (bytevector->string (string->utf16 s 'big) utf-16be-tx))
            (check 'utf-16-test3c s (bytevector->string (string->utf16 s 'little) utf-16le-tx))
            (check 'utf-16-test4a s (bytevector->string (string->bytevector s utf-16-tx) utf-16-tx))
            (check 'utf-16-test4b s (bytevector->string (string->bytevector s utf-16le-tx) utf-16le-tx))
            (check 'utf-16-test4c s (bytevector->string (string->bytevector s utf-16be-tx) utf-16be-tx))
            (check 'utf-16-test5a s (utf16->string (string->utf16 s 'little) 'little))
            (check 'utf-16-test5b s (utf16->string (string->utf16 s 'little) 'little #t))
            (let* ([bv (string->bytevector s utf-16be-tx)]
                   [bvn (bytevector-length bv)]
                   [bv^ (make-bytevector (fx+ bvn 2))])
             ; insert big-endian BOM
              (bytevector-u8-set! bv^ 0 #xfe)
              (bytevector-u8-set! bv^ 1 #xff)
              (bytevector-copy! bv 0 bv^ 2 bvn)
              (check 'utf-16-test6 s (utf16->string bv^ 'big))
              (check 'utf-16-test7 s (bytevector->string bv^ utf-16-tx)))
            (let* ([bv (string->utf16 s 'little)]
                   [bvn (bytevector-length bv)]
                   [bv^ (make-bytevector (fx+ bvn 2))])
             ; insert little-endian BOM
              (bytevector-u8-set! bv^ 0 #xff)
              (bytevector-u8-set! bv^ 1 #xfe)
              (bytevector-copy! bv 0 bv^ 2 bvn)
              (check 'utf-16-test8 s (utf16->string bv^ 'little))
              (check 'utf-16-test9 s (bytevector->string bv^ utf-16-tx)))
          (check 'utf-32-test1a s (utf32->string (string->utf32 s 'big) 'big))
          (check 'utf-32-test1a s (utf32->string (string->utf32 s 'big) 'big #t))
          (check 'utf-32-test2a s (utf32->string (string->utf32 s 'little) 'little))
          (check 'utf-32-test2b s (utf32->string (string->utf32 s 'little) 'little #f)))))))
)

(mat string<->bytevector-conversions
 ; adapted with minor modifications from bv2string.sch, which is:
 ;
 ; Copyright 2007 William D Clinger.
 ;
 ; Permission to copy this software, in whole or in part, to use this
 ; software for any lawful purpose, and to redistribute this software
 ; is granted subject to the restriction that all copies made of this
 ; software must include this copyright notice in full.
 ;
 ; I also request that you send me a copy of any improvements that you
 ; make to this software so that they may be incorporated within it to
 ; the benefit of the Scheme community.
  (begin
    (library (bv2string) (export main)
      (import (rnrs base)
              (rnrs unicode)
              (rename (rnrs bytevectors)
                (utf8->string rnrs:utf8->string)
                (string->utf8 rnrs:string->utf8))
              (rnrs control)
              (rnrs io simple)
              (rnrs mutable-strings))

      ; Crude test rig, just for benchmarking.

      (define utf8->string)
      (define string->utf8)

      (define (test name actual expected)
        (if (not (equal? actual expected))
            (error 'test name)))

      ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
      ;
      ; The R6RS doesn't specify exactly how many replacement
      ; characters get generated by an encoding or decoding error,
      ; so the results of some tests are compared by treating any
      ; sequence of consecutive replacement characters the same as
      ; a single replacement character.
      ;
      ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

      (define (string~? s1 s2)
        (define (replacement? c)
          (char=? c #\xfffd))
        (define (canonicalized s)
          (let loop ((rchars (reverse (string->list s)))
                     (cchars '()))
            (cond ((or (null? rchars) (null? (cdr rchars)))
                   (list->string cchars))
                  ((and (replacement? (car rchars))
                        (replacement? (cadr rchars)))
                   (loop (cdr rchars) cchars))
                  (else
                   (loop (cdr rchars) (cons (car rchars) cchars))))))
        (string=? (canonicalized s1) (canonicalized s2)))

      ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
      ;
      ; Basic sanity tests, followed by stress tests on random inputs.
      ;
      ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

      (define (string-bytevector-tests
               *random-stress-tests* *random-stress-test-max-size*)

        (define (test-roundtrip bvec tostring tobvec)
          (let* ((s1 (tostring bvec))
                 (b2 (tobvec s1))
                 (s2 (tostring b2)))
            (test "round trip of string conversion" (string=? s1 s2) #t)))

        ; This random number generator doesn't have to be good.
        ; It just has to be fast.

        (define random
          (letrec ((random14
                    (lambda (n)
                      (set! x (mod (+ (* a x) c) (+ m 1)))
                      (mod (div x 8) n)))
                   (a 701)
                   (x 1)
                   (c 743483)
                   (m 524287)
                   (loop
                    (lambda (q r n)
                      (if (zero? q)
                          (mod r n)
                          (loop (div q 16384)
                                (+ (* 16384 r) (random14 16384))
                                n)))))
            (lambda (n)
              (if (< n 16384)
                  (random14 n)
                  (loop (div n 16384) (random14 16384) n)))))
       
        ; Returns a random bytevector of length up to n.

        (define (random-bytevector n)
          (let* ((n (random n))
                 (bv (make-bytevector n)))
            (do ((i 0 (+ i 1)))
                ((= i n) bv)
              (bytevector-u8-set! bv i (random 256)))))

        ; Returns a random bytevector of even length up to n.

        (define (random-bytevector2 n)
          (let* ((n (random n))
                 (n (if (odd? n) (+ n 1) n))
                 (bv (make-bytevector n)))
            (do ((i 0 (+ i 1)))
                ((= i n) bv)
              (bytevector-u8-set! bv i (random 256)))))

        ; Returns a random bytevector of multiple-of-4 length up to n.

        (define (random-bytevector4 n)
          (let* ((n (random n))
                 (n (* 4 (round (/ n 4))))
                 (bv (make-bytevector n)))
            (do ((i 0 (+ i 1)))
                ((= i n) bv)
              (bytevector-u8-set! bv i (random 256)))))

        (test "utf-8, BMP"
              (bytevector=? (string->utf8 "k\x007f;\x0080;\x07ff;\x0800;\xffff;")
                            '#vu8(#x6b
                                  #x7f
                                  #b11000010 #b10000000
                                  #b11011111 #b10111111
                                  #b11100000 #b10100000 #b10000000
                                  #b11101111 #b10111111 #b10111111))
              #t)

        (test "utf-8, supplemental"
              (bytevector=? (string->utf8 "\x010000;\x10ffff;")
                            '#vu8(#b11110000 #b10010000 #b10000000 #b10000000
                                  #b11110100 #b10001111 #b10111111 #b10111111))
              #t)

        (test "utf-8, errors 1"
              (string~? (utf8->string '#vu8(#x61                             ; a
                                            #xc0 #x62                        ; ?b
                                            #xc1 #x63                        ; ?c
                                            #xc2 #x64                        ; ?d
                                            #x80 #x65                        ; ?e
                                            #xc0 #xc0 #x66                   ; ??f
                                            #xe0 #x67                        ; ?g
                                           ))
                        "a\xfffd;b\xfffd;c\xfffd;d\xfffd;e\xfffd;\xfffd;f\xfffd;g")
              #t)

        (test "utf-8, errors 2"
              (string~? (utf8->string '#vu8(#xe0 #x80 #x80 #x68              ; ???h
                                            #xe0 #xc0 #x80 #x69              ; ???i
                                            #xf0 #x6a                        ; ?j
                                           ))
                        "\xfffd;\xfffd;\xfffd;h\xfffd;\xfffd;\xfffd;i\xfffd;j")
              #t)

        (test "utf-8, errors 3"
              (string~? (utf8->string '#vu8(#x61                             ; a
                                            #xf0 #x80 #x80 #x80 #x62         ; ????b
                                            #xf0 #x90 #x80 #x80 #x63         ; .c
                                           ))
                        "a\xfffd;\xfffd;\xfffd;\xfffd;b\x10000;c")
              #t)

        (test "utf-8, errors 4"
              (string~? (utf8->string '#vu8(#x61                             ; a
                                            #xf0 #xbf #xbf #xbf #x64         ; .d
                                            #xf0 #xbf #xbf #x65              ; ?e
                                            #xf0 #xbf #x66                   ; ?f
                                           ))
                        "a\x3ffff;d\xfffd;e\xfffd;f")
              #t)

        (test "utf-8, errors 5"
              (string~? (utf8->string '#vu8(#x61                             ; a
                                            #xf4 #x8f #xbf #xbf #x62         ; .b
                                            #xf4 #x90 #x80 #x80 #x63         ; ????c
                                           ))

                        "a\x10ffff;b\xfffd;\xfffd;\xfffd;\xfffd;c")
              #t)

        (test "utf-8, errors 6"
              (string~? (utf8->string '#vu8(#x61                             ; a
                                            #xf5 #x80 #x80 #x80 #x64         ; ????d
                                           ))

                        "a\xfffd;\xfffd;\xfffd;\xfffd;d")
              #t)

        ; ignores BOM signature
        ; Officially, there is no BOM signature for UTF-8,
        ; so this test is commented out.

      #;(test "utf-8, BOM"
              (string=? (utf8->string '#vu8(#xef #xbb #xbf #x61 #x62 #x63 #x64))
                        "abcd")
              #t)

        (test-roundtrip (random-bytevector 10) utf8->string string->utf8)

        (do ((i 0 (+ i 1)))
            ((= i *random-stress-tests*))
          (test-roundtrip (random-bytevector *random-stress-test-max-size*)
                          utf8->string string->utf8))

        (test "utf-16, BMP"
              (bytevector=? (string->utf16 "k\x007f;\x0080;\x07ff;\x0800;\xffff;")
                            '#vu8(#x00 #x6b
                                  #x00 #x7f
                                  #x00 #x80
                                  #x07 #xff
                                  #x08 #x00
                                  #xff #xff))
              #t)

        (test "utf-16le, BMP"
              (bytevector=? (string->utf16 "k\x007f;\x0080;\x07ff;\x0800;\xffff;"
                                           'little)
                            '#vu8(#x6b #x00
                                  #x7f #x00
                                  #x80 #x00
                                  #xff #x07
                                  #x00 #x08
                                  #xff #xff))
              #t)

        (test "utf-16, supplemental"
              (bytevector=? (string->utf16 "\x010000;\xfdcba;\x10ffff;")
                            '#vu8(#xd8 #x00 #xdc #x00
                                  #xdb #xb7 #xdc #xba
                                  #xdb #xff #xdf #xff))
              #t)

        (test "utf-16le, supplemental"
              (bytevector=? (string->utf16 "\x010000;\xfdcba;\x10ffff;" 'little)
                            '#vu8(#x00 #xd8 #x00 #xdc
                                  #xb7 #xdb #xba #xdc
                                  #xff #xdb #xff #xdf))
              #t)

        (test "utf-16be"
              (bytevector=? (string->utf16 "ab\x010000;\xfdcba;\x10ffff;cd")
                            (string->utf16 "ab\x010000;\xfdcba;\x10ffff;cd" 'big))
              #t)

        (test "utf-16, errors 1"
              (string~? "k\x007f;\x0080;\x07ff;\x0800;\xffff;"
                        (utf16->string
                         '#vu8(#x00 #x6b
                               #x00 #x7f
                               #x00 #x80
                               #x07 #xff
                               #x08 #x00
                               #xff #xff)
                         'big))
              #t)

        (test "utf-16, errors 2"
              (string~? "k\x007f;\x0080;\x07ff;\x0800;\xffff;"
                        (utf16->string
                         '#vu8(#x00 #x6b
                               #x00 #x7f
                               #x00 #x80
                               #x07 #xff
                               #x08 #x00
                               #xff #xff)
                         'big #t))
              #t)

        (test "utf-16, errors 3"
              (string~? "k\x007f;\x0080;\x07ff;\x0800;\xffff;"
                        (utf16->string
                         '#vu8(#xfe #xff     ; big-endian BOM
                               #x00 #x6b
                               #x00 #x7f
                               #x00 #x80
                               #x07 #xff
                               #x08 #x00
                               #xff #xff)
                         'big))
              #t)

        (test "utf-16, errors 4"
              (string~? "k\x007f;\x0080;\x07ff;\x0800;\xffff;"
                        (utf16->string
                         '#vu8(#x6b #x00
                               #x7f #x00
                               #x80 #x00
                               #xff #x07
                               #x00 #x08
                               #xff #xff)
                         'little #t))
              #t)

        (test "utf-16, errors 5"
              (string~? "k\x007f;\x0080;\x07ff;\x0800;\xffff;"
                        (utf16->string
                         '#vu8(#xff #xfe     ; little-endian BOM
                               #x6b #x00
                               #x7f #x00
                               #x80 #x00
                               #xff #x07
                               #x00 #x08
                               #xff #xff)
                         'big))
              #t)

        (let ((tostring        (lambda (bv) (utf16->string bv 'big)))
              (tostring-big    (lambda (bv) (utf16->string bv 'big #t)))
              (tostring-little (lambda (bv) (utf16->string bv 'little #t)))
              (tobvec          string->utf16)
              (tobvec-big      (lambda (s) (string->utf16 s 'big)))
              (tobvec-little   (lambda (s) (string->utf16 s 'little))))

          (do ((i 0 (+ i 1)))
              ((= i *random-stress-tests*))
            (test-roundtrip (random-bytevector2 *random-stress-test-max-size*)
                            tostring tobvec)
            (test-roundtrip (random-bytevector2 *random-stress-test-max-size*)
                            tostring-big tobvec-big)
            (test-roundtrip (random-bytevector2 *random-stress-test-max-size*)
                            tostring-little tobvec-little)))

        (test "utf-32"
              (bytevector=? (string->utf32 "abc")
                            '#vu8(#x00 #x00 #x00 #x61
                                  #x00 #x00 #x00 #x62
                                  #x00 #x00 #x00 #x63))
              #t)

        (test "utf-32be"
              (bytevector=? (string->utf32 "abc" 'big)
                            '#vu8(#x00 #x00 #x00 #x61
                                  #x00 #x00 #x00 #x62
                                  #x00 #x00 #x00 #x63))
              #t)

        (test "utf-32le"
              (bytevector=? (string->utf32 "abc" 'little)
                            '#vu8(#x61 #x00 #x00 #x00
                                  #x62 #x00 #x00 #x00
                                  #x63 #x00 #x00 #x00))
              #t)

        (test "utf-32, errors 1"
              (string~? "a\xfffd;b\xfffd;c\xfffd;d\xfffd;e"
                        (utf32->string
                         '#vu8(#x00 #x00 #x00 #x61
                               #x00 #x00 #xd9 #x00
                               #x00 #x00 #x00 #x62
                               #x00 #x00 #xdd #xab
                               #x00 #x00 #x00 #x63
                               #x00 #x11 #x00 #x00
                               #x00 #x00 #x00 #x64
                               #x01 #x00 #x00 #x65
                               #x00 #x00 #x00 #x65)
                         'big))
              #t)

        (test "utf-32, errors 2"
              (string~? "a\xfffd;b\xfffd;c\xfffd;d\xfffd;e"
                        (utf32->string
                         '#vu8(#x00 #x00 #x00 #x61
                               #x00 #x00 #xd9 #x00
                               #x00 #x00 #x00 #x62
                               #x00 #x00 #xdd #xab
                               #x00 #x00 #x00 #x63
                               #x00 #x11 #x00 #x00
                               #x00 #x00 #x00 #x64
                               #x01 #x00 #x00 #x65
                               #x00 #x00 #x00 #x65)
                         'big #t))
              #t)

        (test "utf-32, errors 3"
              (string~? "a\xfffd;b\xfffd;c\xfffd;d\xfffd;e"
                        (utf32->string
                         '#vu8(#x00 #x00 #xfe #xff   ; big-endian BOM
                               #x00 #x00 #x00 #x61
                               #x00 #x00 #xd9 #x00
                               #x00 #x00 #x00 #x62
                               #x00 #x00 #xdd #xab
                               #x00 #x00 #x00 #x63
                               #x00 #x11 #x00 #x00
                               #x00 #x00 #x00 #x64
                               #x01 #x00 #x00 #x65
                               #x00 #x00 #x00 #x65)
                         'big))
              #t)

        (test "utf-32, errors 4"
              (string~? "\xfeff;a\xfffd;b\xfffd;c\xfffd;d\xfffd;e"
                        (utf32->string
                         '#vu8(#x00 #x00 #xfe #xff   ; big-endian BOM
                               #x00 #x00 #x00 #x61
                               #x00 #x00 #xd9 #x00
                               #x00 #x00 #x00 #x62
                               #x00 #x00 #xdd #xab
                               #x00 #x00 #x00 #x63
                               #x00 #x11 #x00 #x00
                               #x00 #x00 #x00 #x64
                               #x01 #x00 #x00 #x65
                               #x00 #x00 #x00 #x65)
                         'big #t))
              #t)

        (test "utf-32, errors 5"
              (string~? "a\xfffd;b\xfffd;c\xfffd;d\xfffd;e"
                        (utf32->string
                         '#vu8(#x61 #x00 #x00 #x00
                               #x00 #xd9 #x00 #x00
                               #x62 #x00 #x00 #x00
                               #xab #xdd #x00 #x00
                               #x63 #x00 #x00 #x00
                               #x00 #x00 #x11 #x00
                               #x64 #x00 #x00 #x00
                               #x65 #x00 #x00 #x01
                               #x65 #x00 #x00 #x00)
                         'little #t))
              #t)

        (test "utf-32, errors 6"
              (string~? "a\xfffd;b\xfffd;c\xfffd;d\xfffd;e"
                        (utf32->string
                         '#vu8(#xff #xfe #x00 #x00   ; little-endian BOM
                               #x61 #x00 #x00 #x00
                               #x00 #xd9 #x00 #x00
                               #x62 #x00 #x00 #x00
                               #xab #xdd #x00 #x00
                               #x63 #x00 #x00 #x00
                               #x00 #x00 #x11 #x00
                               #x64 #x00 #x00 #x00
                               #x65 #x00 #x00 #x01
                               #x65 #x00 #x00 #x00)
                         'big))
              #t)

        (test "utf-32, errors 7"
              (string~? "\xfeff;a\xfffd;b\xfffd;c\xfffd;d\xfffd;e"
                        (utf32->string
                         '#vu8(#xff #xfe #x00 #x00   ; little-endian BOM
                               #x61 #x00 #x00 #x00
                               #x00 #xd9 #x00 #x00
                               #x62 #x00 #x00 #x00
                               #xab #xdd #x00 #x00
                               #x63 #x00 #x00 #x00
                               #x00 #x00 #x11 #x00
                               #x64 #x00 #x00 #x00
                               #x65 #x00 #x00 #x01
                               #x65 #x00 #x00 #x00)
                         'little #t))
              #t)

        (let ((tostring        (lambda (bv) (utf32->string bv 'big)))
              (tostring-big    (lambda (bv) (utf32->string bv 'big #t)))
              (tostring-little (lambda (bv) (utf32->string bv 'little #t)))
              (tobvec          string->utf32)
              (tobvec-big      (lambda (s) (string->utf32 s 'big)))
              (tobvec-little   (lambda (s) (string->utf32 s 'little))))

          (do ((i 0 (+ i 1)))
              ((= i *random-stress-tests*))
            (test-roundtrip (random-bytevector4 *random-stress-test-max-size*)
                            tostring tobvec)
            (test-roundtrip (random-bytevector4 *random-stress-test-max-size*)
                            tostring-big tobvec-big)
            (test-roundtrip (random-bytevector4 *random-stress-test-max-size*)
                            tostring-little tobvec-little)))

      )

      ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
      ;
      ; Exhaustive tests.
      ;
      ; Tests string <-> bytevector conversion on strings
      ; that contain every Unicode scalar value.
      ;
      ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

      (define (exhaustive-string-bytevector-tests)

        ; Tests throughout an inclusive range.

        (define (test-char-range lo hi tostring tobytevector)
          (let* ((n (+ 1 (- hi lo)))
                 (s (make-string n))
                 (replacement-character (integer->char #xfffd)))
            (do ((i lo (+ i 1)))
                ((> i hi))
              (let ((c (if (or (<= 0 i #xd7ff)
                               (<= #xe000 i #x10ffff))
                           (integer->char i)
                           replacement-character)))
                (string-set! s (- i lo) c)))
            (test "test of long string conversion"
                  (string=? (tostring (tobytevector s)) s) #t)))

        (define (test-exhaustively name tostring tobytevector)
         ;(display "Testing ")
         ;(display name)
         ;(display " conversions...")
         ;(newline)
          (test-char-range 0 #xffff tostring tobytevector)
          (test-char-range #x10000 #x1ffff tostring tobytevector)
          (test-char-range #x20000 #x2ffff tostring tobytevector)
          (test-char-range #x30000 #x3ffff tostring tobytevector)
          (test-char-range #x40000 #x4ffff tostring tobytevector)
          (test-char-range #x50000 #x5ffff tostring tobytevector)
          (test-char-range #x60000 #x6ffff tostring tobytevector)
          (test-char-range #x70000 #x7ffff tostring tobytevector)
          (test-char-range #x80000 #x8ffff tostring tobytevector)
          (test-char-range #x90000 #x9ffff tostring tobytevector)
          (test-char-range #xa0000 #xaffff tostring tobytevector)
          (test-char-range #xb0000 #xbffff tostring tobytevector)
          (test-char-range #xc0000 #xcffff tostring tobytevector)
          (test-char-range #xd0000 #xdffff tostring tobytevector)
          (test-char-range #xe0000 #xeffff tostring tobytevector)
          (test-char-range #xf0000 #xfffff tostring tobytevector)
          (test-char-range #x100000 #x10ffff tostring tobytevector))

        ; Feel free to replace this with your favorite timing macro.

        (define (timeit x) x)

        (timeit (test-exhaustively "UTF-8" utf8->string string->utf8))

        ; NOTE:  An unfortunate misunderstanding led to a late deletion
        ; of single-argument utf16->string from the R6RS.  To get the
        ; correct effect of single-argument utf16->string, you have to
        ; use two arguments, as below.
        ;
        ;(timeit (test-exhaustively "UTF-16" utf16->string string->utf16))

        (timeit (test-exhaustively "UTF-16"
                                   (lambda (bv) (utf16->string bv 'big))
                                   string->utf16))

        ; NOTE:  To get the correct effect of two-argument utf16->string,
        ; you have to use three arguments, as below.

        (timeit (test-exhaustively "UTF-16BE"
                                   (lambda (bv) (utf16->string bv 'big #t))
                                   (lambda (s) (string->utf16 s 'big))))

        (timeit (test-exhaustively "UTF-16LE"
                                   (lambda (bv) (utf16->string bv 'little #t))
                                   (lambda (s) (string->utf16 s 'little))))

        ; NOTE:  An unfortunate misunderstanding led to a late deletion
        ; of single-argument utf32->string from the R6RS.  To get the
        ; correct effect of single-argument utf32->string, you have to
        ; use two arguments, as below.
        ;
        ;(timeit (test-exhaustively "UTF-32" utf32->string string->utf32))

        (timeit (test-exhaustively "UTF-32"
                                   (lambda (bv) (utf32->string bv 'big))
                                   string->utf32))

        ; NOTE:  To get the correct effect of two-argument utf32->string,
        ; you have to use three arguments, as below.

        (timeit (test-exhaustively "UTF-32BE"
                                   (lambda (bv) (utf32->string bv 'big #t))
                                   (lambda (s) (string->utf32 s 'big))))

        (timeit (test-exhaustively "UTF-32LE"
                                   (lambda (bv) (utf32->string bv 'little #t))
                                   (lambda (s) (string->utf32 s 'little)))))

      (define (main p1 p2)
        (set! utf8->string p1)
        (set! string->utf8 p2)
        (string-bytevector-tests 2 1000)
        (exhaustive-string-bytevector-tests)))
    #t)
 ; first test w/built-in utf8->string and string->utf8
  (begin
    (let () (import (bv2string)) (main utf8->string string->utf8))
    #t)
 ; next test w/utf8->string and string->utf8 synthesized from utf-8-codec
  (let ()
    (define (utf8->string bv)
      (get-string-all (open-bytevector-input-port bv
                        (make-transcoder (utf-8-codec) 'none))))
    (define (string->utf8 s)
      (let-values ([(op get) (open-bytevector-output-port
                               (make-transcoder (utf-8-codec) 'none))])
        (put-string op s)
        (get)))
    (let () (import (bv2string)) (main utf8->string string->utf8))
    #t)
)

(mat open-process-ports ; see also unix.ms (mat nonblocking ...)
  (begin
    (define ($check-port p xput-port? bt-port?)
      (define-syntax err? 
        (syntax-rules ()
          [(_ e1 e2 ...) (guard (c [#t #t]) e1 e2 ... #f)]))
      (unless (and (xput-port? p) (bt-port? p) (file-port? p))
        (errorf #f "~s is not as it should be" p))
      (let ([fd (port-file-descriptor p)])
        (unless (fixnum? fd)
          (errorf #f "unexpected file descriptor ~s" fd)))
      (when (or (port-has-port-position? p)
                (port-has-set-port-position!? p)
                (port-has-port-length? p)
                (port-has-set-port-length!? p))
        (errorf #f "unexpected port-has-xxx results for ~s" p))
      (unless (and (err? (port-position p))
                   (err? (set-port-position! p 0))
                   (err? (port-length p))
                   (err? (set-port-length! p 0)))
        (errorf #f "no error for getting/setting port position/length on ~s" p)))
    (define $emit-dot
      (let ([n 0])
        (lambda ()
          (display ".")
          (set! n (modulo (+ n 1) 72))
          (when (= n 0) (newline))
          (flush-output-port))))
    #t)
 ; test binary ports
  (let-values ([(to-stdin from-stdout from-stderr pid)
                (open-process-ports (patch-exec-path $cat_flush))])
    (define put-string
      (lambda (bp s)
        (put-bytevector bp (string->utf8 s))))
    (define get-string-some
      (lambda (bp)
        (let ([x (get-bytevector-some bp)])
          (if (eof-object? x) x (utf8->string x)))))
    (define get-string-n
      (lambda (bp n)
        (let ([x (get-bytevector-n bp n)])
          (if (eof-object? x) x (utf8->string x)))))
    (dynamic-wind
      void
      (lambda ()
        (put-string to-stdin "life in the fast lane\n")
        (flush-output-port to-stdin)
        (let f ()
          ($check-port to-stdin output-port? binary-port?)
          ($check-port from-stdout input-port? binary-port?)
          ($check-port from-stderr input-port? binary-port?)
          (when (input-port-ready? from-stderr)
            (errorf #f "input ready on from-stderr ~s" (get-string-some from-stderr)))
          (if (input-port-ready? from-stdout)
              (let ([s (get-string-n from-stdout 10)])
                (unless (equal? s "life in th")
                  (errorf #f "unexpected from-stdout string ~s" s)))
              (begin
                ($emit-dot)
                (f))))
        (let f ([all ""])
          (unless (equal? all "e fast lane\n")
            (when (input-port-ready? from-stderr)
              (errorf #f "input ready on from-stderr ~s" (get-string-some from-stderr)))
            (let ([s (get-string-some from-stdout)])
              ($emit-dot)
              (f (string-append all s)))))
        (and
          (not (input-port-ready? from-stderr))
          (not (input-port-ready? from-stdout))
          (begin
            (close-port to-stdin)
            (let f ()
              (unless (and (port-eof? from-stdout) (port-eof? from-stderr))
                ($emit-dot)
                (f)))
            #t)))
      (lambda ()
        (close-port to-stdin)
        (close-port from-stdout)
        (close-port from-stderr))))
 ; test binary ports w/buffer-mode none
  (let-values ([(to-stdin from-stdout from-stderr pid)
                (open-process-ports (patch-exec-path $cat_flush) (buffer-mode none))])
    (define put-string
      (lambda (bp s)
        (put-bytevector bp (string->utf8 s))))
    (define get-string-some
      (lambda (bp)
        (let ([x (get-bytevector-some bp)])
          (if (eof-object? x) x (utf8->string x)))))
    (define get-string-n
      (lambda (bp n)
        (let ([x (get-bytevector-n bp n)])
          (if (eof-object? x) x (utf8->string x)))))
    (dynamic-wind
      void
      (lambda ()
        ($check-port to-stdin output-port? binary-port?)
        ($check-port from-stdout input-port? binary-port?)
        ($check-port from-stderr input-port? binary-port?)
        (put-string to-stdin "life in the fast lane\n")
        (flush-output-port to-stdin)
        (let f ()
          (when (input-port-ready? from-stderr)
            (errorf #f "input ready on from-stderr ~s" (get-string-some from-stderr)))
          (if (input-port-ready? from-stdout)
              (let ([s (get-string-n from-stdout 10)])
                (unless (equal? s "life in th")
                  (errorf #f "unexpected from-stdout string ~s" s)))
              (begin
                ($emit-dot)
                (f))))
        (let f ([all ""])
          (unless (equal? all "e fast lane\n")
            (when (input-port-ready? from-stderr)
              (errorf #f "input ready on from-stderr ~s" (get-string-some from-stderr)))
            (let ([s (get-string-some from-stdout)])
              ($emit-dot)
              (f (string-append all s)))))
        (and
          (not (input-port-ready? from-stderr))
          (not (input-port-ready? from-stdout))
          (begin
            (close-port to-stdin)
            (let f ()
              (unless (and (port-eof? from-stdout) (port-eof? from-stderr))
                ($emit-dot)
                (f)))
            #t)))
      (lambda ()
        (close-port to-stdin)
        (close-port from-stdout)
        (close-port from-stderr))))
 ; test textual ports
  (let-values ([(to-stdin from-stdout from-stderr pid)
                (open-process-ports (patch-exec-path $cat_flush) (buffer-mode block) (native-transcoder))])
    (dynamic-wind
      void
      (lambda ()
        ($check-port to-stdin output-port? textual-port?)
        ($check-port from-stdout input-port? textual-port?)
        ($check-port from-stderr input-port? textual-port?)
        (put-string to-stdin "life in the fast lane\n")
        (flush-output-port to-stdin)
        (let f ()
          (when (input-port-ready? from-stderr)
            (errorf #f "input ready on from-stderr ~s" (get-string-some from-stderr)))
          (if (input-port-ready? from-stdout)
              (let ([s (get-string-n from-stdout 10)])
                (unless (equal? s "life in th")
                  (errorf #f "unexpected from-stdout string ~s" s)))
              (begin
                ($emit-dot)
                (f))))
        (let f ([all ""])
          (unless (equal? all "e fast lane\n")
            (when (input-port-ready? from-stderr)
              (errorf #f "input ready on from-stderr ~s" (get-string-some from-stderr)))
            (let ([s (get-string-some from-stdout)])
              ($emit-dot)
              (f (string-append all s)))))
        (and
          (not (input-port-ready? from-stderr))
          (not (input-port-ready? from-stdout))
          (begin
            (close-port to-stdin)
            (let f ()
              (unless (and (port-eof? from-stdout) (port-eof? from-stderr))
                ($emit-dot)
                (f)))
            #t)))
      (lambda ()
        (close-port to-stdin)
        (close-port from-stdout)
        (close-port from-stderr))))
 ; test textual ports w/buffer-mode none
  (let-values ([(to-stdin from-stdout from-stderr pid)
                (open-process-ports (patch-exec-path $cat_flush) (buffer-mode none) (native-transcoder))])
    (dynamic-wind
      void
      (lambda ()
        ($check-port to-stdin output-port? textual-port?)
        ($check-port from-stdout input-port? textual-port?)
        ($check-port from-stderr input-port? textual-port?)
        (put-string to-stdin "life in the fast lane\n")
        (flush-output-port to-stdin)
        (let f ()
          (when (input-port-ready? from-stderr)
            (errorf #f "input ready on from-stderr ~s" (get-string-some from-stderr)))
          (if (input-port-ready? from-stdout)
              (let ([s (get-string-n from-stdout 10)])
                (unless (equal? s "life in th")
                  (errorf #f "unexpected from-stdout string ~s" s)))
              (begin
                ($emit-dot)
                (f))))
        (let f ([all ""])
          (unless (equal? all "e fast lane\n")
            (when (input-port-ready? from-stderr)
              (errorf #f "input ready on from-stderr ~s" (get-string-some from-stderr)))
            (let ([s (get-string-some from-stdout)])
              ($emit-dot)
              (f (string-append all s)))))
        (and
          (not (input-port-ready? from-stderr))
          (not (input-port-ready? from-stdout))
          (begin
            (close-port to-stdin)
            (let f ()
              (unless (and (port-eof? from-stdout) (port-eof? from-stderr))
                ($emit-dot)
                (f)))
            #t)))
      (lambda ()
        (close-port to-stdin)
        (close-port from-stdout)
        (close-port from-stderr))))
 ; test textual ports w/buffer-mode line
  (let-values ([(to-stdin from-stdout from-stderr pid)
                (open-process-ports (patch-exec-path $cat_flush) (buffer-mode line) (native-transcoder))])
    (dynamic-wind
      void
      (lambda ()
        ($check-port to-stdin output-port? textual-port?)
        ($check-port from-stdout input-port? textual-port?)
        ($check-port from-stderr input-port? textual-port?)
        (put-string to-stdin "life in the fast lane\n")
        (flush-output-port to-stdin)
        (let f ()
          (when (input-port-ready? from-stderr)
            (errorf #f "input ready on from-stderr ~s" (get-string-some from-stderr)))
          (if (input-port-ready? from-stdout)
              (let ([s (get-string-n from-stdout 10)])
                (unless (equal? s "life in th")
                  (errorf #f "unexpected from-stdout string ~s" s)))
              (begin
                ($emit-dot)
                (f))))
        (let f ([all ""])
          (unless (equal? all "e fast lane\n")
            (when (input-port-ready? from-stderr)
              (errorf #f "input ready on from-stderr ~s" (get-string-some from-stderr)))
            (let ([s (get-string-some from-stdout)])
              ($emit-dot)
              (f (string-append all s)))))
        (and
          (not (input-port-ready? from-stderr))
          (not (input-port-ready? from-stdout))
          (begin
            (close-port to-stdin)
            (let f ()
              (unless (and (port-eof? from-stdout) (port-eof? from-stderr))
                ($emit-dot)
                (f)))
            #t)))
      (lambda ()
        (close-port to-stdin)
        (close-port from-stdout)
        (close-port from-stderr))))
)

(mat to-fold-or-not-to-fold
  (begin
    (define ($readit cs? s)
      (define (string-append* s1 . ls)
        (let f ([s1 s1] [ls ls] [n 0])
          (let ([n1 (string-length s1)])
            (if (null? ls)
                (let ([s (make-string (fx+ n n1))])
                  (string-copy! s1 0 s n n1)
                  s)
                (let ([s (f (car ls) (cdr ls) (fx+ n n1 1))])
                  (string-copy! s1 0 s n n1)
                  (string-set! s (fx+ n n1) #\$)
                  s)))))
      (apply string-append*
        (let ([sip (open-input-string s)])
          (parameterize ([case-sensitive cs?])
            (let f ()
              (let ([x (get-datum sip)])
                (if (eof-object? x)
                    '()
                    (cons (cond
                            [(gensym? x)
                             (string-append (symbol->string x) "%"
                               (gensym->unique-string x))]
                            [(symbol? x) (symbol->string x)]
                            [(char? x) (string x)]
                            [else (error 'string-append* "unexpected ~s" x)])
                          (f)))))))))
    #t)
  (case-sensitive)
  (equal?
    ($readit #t "To be or NOT to bE")
    "To$be$or$NOT$to$bE")
  (equal?
    ($readit #f "To be or NOT to bE")
    "to$be$or$not$to$be")
  (equal?
    ($readit #t "To be #!no-fold-case or NOT #!fold-case to bE")
    "To$be$or$NOT$to$be")
  (equal?
    ($readit #t "To be #!fold-case or NOT #!no-fold-case to bE")
    "To$be$or$not$to$bE")
  (equal?
    ($readit #f "To be #!no-fold-case or NOT #!fold-case to bE")
    "to$be$or$NOT$to$be")
  (equal?
    ($readit #f "To be #!fold-case or NOT #!no-fold-case to bE")
    "to$be$or$not$to$bE")
 ; check delimiting
  (equal?
    ($readit #f "To be#!fold-caseor NOT#!no-fold-caseto bE")
    "to$be$or$not$to$bE")
 ; verify case folding is not disabled when Unicode hex escape seen
  (equal?
    ($readit #t "ab\\x43;de")
    "abCde")
  (equal?
    ($readit #f "ab\\x43;de")
    "abcde")
  (equal?
    ($readit #t "#!fold-case ab\\x43;de")
    "abcde")
  (equal?
    ($readit #f "#!fold-case ab\\x43;de")
    "abcde")
  (equal?
    ($readit #t "#!no-fold-case ab\\x43;de")
    "abCde")
  (equal?
    ($readit #f "#!no-fold-case ab\\x43;de")
    "abCde")
 ; verify case folding still works when string changes size
  (equal?
    ($readit #t "Stra\xDF;e #!no-fold-case Stra\xDF;e #!fold-case Stra\xDF;e")
    "Stra\xDF;e$Stra\xDF;e$strasse")
  (equal?
    ($readit #f "Stra\xDF;e #!no-fold-case Stra\xDF;e #!fold-case Stra\xDF;e")
    "strasse$Stra\xDF;e$strasse")
  (equal?
    ($readit #t "Stra\xDF;e #!fold-case Stra\xDF;e #!no-fold-case Stra\xDF;e")
    "Stra\xDF;e$strasse$Stra\xDF;e")
  (equal?
    ($readit #f "Stra\xDF;e #!fold-case Stra\xDF;e #!no-fold-case Stra\xDF;e")
    "strasse$strasse$Stra\xDF;e")
  (equal?
    ($readit #t "Aa|b C|dE abC|D e|Fg \\#Ab\\ C a\\B\\ cd")
    "Aab CdE$abCD eFg$#Ab C$aB cd")
 ; verify case folding is disabled when vertical bars or backslashes
 ; (other than those for Unicode hex escapes) appear
  (equal?
    ($readit #f "Aa|b C|dE abC|D e|Fg \\#Ab\\ C a\\B\\ cd")
    "Aab CdE$abCD eFg$#Ab C$aB cd")
  (equal?
    ($readit #t "#!fold-case Aa|b C|dE abC|D e|Fg \\#Ab\\ C a\\B\\ cd")
    "Aab CdE$abCD eFg$#Ab C$aB cd")
  (equal?
    ($readit #f "#!fold-case Aa|b C|dE abC|D e|Fg \\#Ab\\ C a\\B\\ cd")
    "Aab CdE$abCD eFg$#Ab C$aB cd")
  (equal?
    ($readit #t "#!no-fold-case Aa|b C|dE abC|D e|Fg \\#Ab\\ C a\\B\\ cd")
    "Aab CdE$abCD eFg$#Ab C$aB cd")
  (equal?
    ($readit #f "#!no-fold-case Aa|b C|dE abC|D e|Fg \\#Ab\\ C a\\B\\ cd")
    "Aab CdE$abCD eFg$#Ab C$aB cd")
 ; verify proper case folding for gensyms
  (equal?
    ($readit #t "#{aBc DeF1}")
    "aBc%DeF1")
  (equal?
    ($readit #f "#{aBc DeF2}")
    "abc%def2")
  (equal?
    ($readit #t "#!fold-case #{aBc DeF3}")
    "abc%def3")
  (equal?
    ($readit #f "#!fold-case #{aBc DeF4}")
    "abc%def4")
  (equal?
    ($readit #t "#!no-fold-case #{aBc DeF5}")
    "aBc%DeF5")
  (equal?
    ($readit #f "#!no-fold-case #{aBc DeF6}")
    "aBc%DeF6")
  (equal?
    ($readit #t "#{aBc De\\F7}")
    "aBc%DeF7")
  (equal?
    ($readit #f "#{aBc De\\F8}")
    "abc%DeF8")
  (equal?
    ($readit #t "#!fold-case #{aBc De\\F9}")
    "abc%DeF9")
  (equal?
    ($readit #f "#!fold-case #{aBc De\\F10}")
    "abc%DeF10")
  (equal?
    ($readit #t "#!no-fold-case #{aBc De\\F11}")
    "aBc%DeF11")
  (equal?
    ($readit #f "#!no-fold-case #{aBc De\\F12}")
    "aBc%DeF12")
  (equal?
    ($readit #t "#{a\\Bc DeF13}")
    "aBc%DeF13")
  (equal?
    ($readit #f "#{a\\Bc DeF14}")
    "aBc%def14")
  (equal?
    ($readit #t "#!fold-case #{a\\Bc DeF15}")
    "aBc%def15")
  (equal?
    ($readit #f "#!fold-case #{a\\Bc DeF16}")
    "aBc%def16")
  (equal?
    ($readit #t "#!no-fold-case #{a\\Bc DeF17}")
    "aBc%DeF17")
  (equal?
    ($readit #f "#!no-fold-case #{a\\Bc DeF18}")
    "aBc%DeF18")
  (equal?
    ($readit #t "#{a\\Bc De\\F19}")
    "aBc%DeF19")
  (equal?
    ($readit #f "#{a\\Bc De\\F20}")
    "aBc%DeF20")
  (equal?
    ($readit #t "#!fold-case #{a\\Bc De\\F21}")
    "aBc%DeF21")
  (equal?
    ($readit #f "#!fold-case #{a\\Bc De\\F22}")
    "aBc%DeF22")
  (equal?
    ($readit #t "#!no-fold-case #{a\\Bc De\\F23}")
    "aBc%DeF23")
  (equal?
    ($readit #f "#!no-fold-case #{a\\Bc De\\F24}")
    "aBc%DeF24")
  (equal?
    ($readit #t "#\\newline")
    "\n")
  (equal?
    ($readit #f "#\\newline")
    "\n")
  (equal?
    ($readit #f "#!fold-case #\\newline")
    "\n")
  (equal?
    ($readit #f "#!fold-case #\\newline")
    "\n")
  (equal?
    ($readit #f "#!no-fold-case #\\newline")
    "\n")
  (equal?
    ($readit #f "#!no-fold-case #\\newline")
    "\n")
  (error? ($readit #t "#\\newLine"))
  (equal?
    ($readit #f "#\\newLine")
    "\n")
  (equal?
    ($readit #t "#!fold-case #\\newLine")
    "\n")
  (equal?
    ($readit #f "#!fold-case #\\newLine")
    "\n")
  (error? ($readit #t "#!no-fold-case #\\newLine"))
  (error? ($readit #f "#!no-fold-case #\\newLine"))
)
